Table of Contents

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 (an IManagerSource)
  • 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 extend TypeMapManager.

ManagerSource<TManageable, TManager>

A reusable base for writing custom IManagerSources.
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.