Manager Abstraction
Namespaces: RisingV.Shared.Managers
, RisingV.Shared.Extensions
, RisingV.Shared.Collections
Assemblies: RisingV.Shared
The manager framework gives RisingV plugins a uniform, pluggable way to register, track, and hot‑reload feature modules (“manageable objects”). It is built around five core ideas:
Concept | Purpose |
---|---|
IManageable | Marker interface for anything that belongs to a manager. |
IManager | Orchestrator that owns one or more manageables. |
IManagerSource | Pluggable repository that stores and enumerates manageables. |
ManagerBase<T, TM> | Generic skeleton that implements the full lifecycle and event hooks. |
TypeMapManager<T, TM> | Ready‑made base using an insertion‑ordered map for storage. |
Together they let you spin up new subsystems in minutes while preserving discoverability, dependency injection, and reload safety.
1. Key Interfaces
IManageable & IManageable<TManager>
public interface IManageable { }
public interface IManageable<in TManager> : IManageable
where TManager : class, IManager
{ }
Any class that should be owned by a manager implements one of these. The generic flavour lets the manageable know which manager type controls it, unlocking strongly‑typed callbacks later on.
IManager
public interface IManager
{
bool IsInitialized { get; }
bool IsLoaded { get; }
bool IsReady { get; }
void Initialize(IPlugin plugin);
void Load(IPlugin plugin);
void Ready(IPlugin plugin);
void Reload(IPlugin plugin, ReloadReason reason);
void Unload(IPlugin plugin);
void Terminate(IPlugin plugin);
}
Every manager exposes the 6‑stage lifecycle (see §3) plus a few helpers (Add/Remove/Get/Has) that delegate to its source.
IReloadable / ReloadReason
Manageables may implement IReloadable
to react to hot‑reload signals
triggered by file changes or admin commands.
public sealed record ReloadReason(
string Reason,
IReadOnlyList<FileChange>? Changes = null,
bool IsForced = false,
bool IsCritical = false,
bool IsFull = false,
string? Key = null);
ITypeSource
A minimal CRUD abstraction returning instances keyed by Type
.
Managers compose one of these so they stay agnostic of the underlying store.
2. Core Classes
ManagerBase<TManageable, TSelf>
An abstract class that:
- Implements
IManager
- Tracks state (
IsInitialized
,IsLoaded
,IsReady
) - Subscribes/unsubscribes to global events via
EventBridge
- Delegates storage to
Source
(anIManagerSource
) - Provides convenience wrappers:
public TX? Add<TX>(IPlugin plugin) where TX : TManageable;
public void Add<TX>(IPlugin plugin, TX obj);
public bool Remove<TX>() where TX : TManageable;
public TX? Get<TX>(bool required = true) where TX : TManageable;
public bool Has<TX>() where TX : TManageable;
Tip – derive directly from
ManagerBase
only if you need exotic storage. Most projects should extendTypeMapManager
.
ManagerSource<TManageable, TManager>
A reusable base for writing custom IManagerSource
s.
Takes care of plugin ownership and logging.
TypeMapManager<TManageable, TManager>
The batteries‑included implementation.
Internally it uses an OrderedMap<Type,T>
so enumeration order
matches insertion order, making debugging predictable and enabling simple
LRU‑style eviction if you ever need it.
public abstract class CombatSystemManager
: TypeMapManager<ICombatSystem, CombatSystemManager>
{
protected override Logger Log { get; } =
Logger.Create<CombatSystemManager>();
}
3. The Manager Lifecycle
┌──────────┐ ┌──────────┐ ┌──────────┐
│Initialize│ ──► │ Load │ ──► │ Ready │
└──────────┘ └──────────┘ └──────────┘
▲ │ │
│ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│Terminate │ ◄── │ Unload │ ◄── │ Reload │
└──────────┘ └──────────┘ └──────────┘
Stage | Typical Responsibility |
---|---|
Initialize | Allocate the Source , register default manageables. |
Load | Wire external dependencies, read config files. |
Ready | Final handshake after all managers are loaded – safe point to emit events. |
Reload | Refresh config or state without a full restart (hot‑swap). |
Unload | Free resources, unsubscribe events. |
Terminate | Final shutdown (called once). |
All state transitions are idempotent – calling Load
twice
does nothing once IsLoaded=true
.
4. Creating Your Own Manager
public interface IWeaponSystem : IManageable<WeaponManager> { }
public sealed class RangedSystem : IWeaponSystem { /* … */ }
public sealed class WeaponManager
: TypeMapManager<IWeaponSystem, WeaponManager>
{
protected override Logger Log { get; } = Logger.Create<WeaponManager>();
public override void Initialize(IPlugin plugin)
{
base.Initialize(plugin);
Add<RangedSystem>(plugin); // auto‑instantiated
}
}
And from your plugin OnInitialize
:
var weaponManager = AddSharedComponent<WeaponManager>(this);
Hot‑reload support:
weaponManager.Reload(this,
new ReloadReason("Config changed", changes: fileChanges, isFull:false));
5. Best Practices
- One responsibility per manageable – keeps dependency graphs shallow.
- Prefer
TypeMapManager
unless you need multi‑key or sharded storage. - Implement
IReloadable
on manageables for zero‑downtime tweaks. - Toggle subsystems at runtime by exposing an
Enabled
config entry. - Call
Ready
only after every manager has loaded to avoid race conditions.
6. Troubleshooting
Symptom | Likely Cause | Fix |
---|---|---|
MissingRequiredException on Ready |
Dependency manager not loaded | Check plugin load order, call Add in Initialize |
Repeated Load logs |
Manager called twice | Guard with IsLoaded (handled by base class but avoid manual calls) |
No reaction to file edits | Manageable lacks IReloadable |
Implement Reload and return true from OnReload |
TL;DR
The manager abstraction lets you compose complex subsystems from small, self‑contained units and still enjoy:
- Deterministic lifecycle hooks
- Hot‑reload with zero downtime
- Strong compile‑time typing (no string keys)
- Centralised logging and diagnostics
Use it to keep your RisingV codebase organised, testable, and future‑proof.