Table of Contents

Processor Abstraction

Namespaces: RisingV.Shared.Processors, RisingV.Shared.Managers
Assembly: RisingV.Shared

The Processor abstraction gives RisingV a lightweight, opt‑in pipeline for running discrete logic units (processors) before and after important operations—combat rolls, economy ticks, command execution, you name it—while plugging straight into the familiar Manager lifecycle.

Plugin
  └─ ProcessorManager
       ├─ DamageProcessor
       ├─ LootProcessor
       └─ AnalyticsProcessor

Each processor decides whether it can handle an incoming event and then returns a ProcessToken (Continue, Skip, or Cancel) plus an optional result back to the caller.


1. Key Interfaces & Types

Contract Purpose
IProcessor Marker that ties a processor to ProcessorManager.
IProcessor<T, TR> Generic handler for events of type T producing result TR.
ProcessToken Enum instructing the caller to Continue, Skip, or Cancel further processing.
ProcessorBase<T, TR> Abstract utility base that wires common boiler‑plate.
DefaultProcessor<T> Convenience type alias of ProcessorBase<T, bool?> – useful for yes/no flows.
ProcessorManager A TypeMapManager implementation that registers processors and orchestrates lifecycle.

IProcessor<T, TR>

public interface IProcessor<in T, TR> : IProcessor
{
    bool CanProcess(T @event, bool isPost);
    ProcessToken PostProcess(T @event, out TR? result);
}
  • @event – the incoming domain object.
  • isPostfalse for pre‑processing, true for post‑processing.
  • result – custom output, may be null.

ProcessToken

Value Behaviour
Continue Proceed to next processor / default logic.
Skip Stop executing further processors but let default logic run.
Cancel Abort the entire operation; caller should roll back.

2. ProcessorBase<T, TR>

public abstract class ProcessorBase<T, TR> : IProcessor<T, TR>
{
    public abstract bool CanProcess(T @event, bool isPost);
    public abstract ProcessToken PostProcess(T @event, out TR? result);

    // Optional helper for pre‑processing
    public virtual ProcessToken PreProcess(T @event, out TR? result)
        => ProcessToken.Continue;
}

Override the two abstract methods and you’re done.
ProcessorBase supplies no‑op default implementations so you only implement the bits you need.


3. ProcessorManager

public class ProcessorManager
    : TypeMapManager<IProcessor, ProcessorManager>, IPluginComponent
{
    // Add/Remove/Get helpers generated for clarity
    public T?  AddProcessor<T>(IPlugin p, bool fail=true) where T : IProcessor;
    public void RemoveProcessor<T>(IPlugin p)             where T : IProcessor;
    public T?  GetProcessor<T>(bool required=true)        where T : IProcessor;
}

It inherits the full Initialize → Load → Ready → Reload → Unload → Terminate state machine from ManagerBase, so you can treat it just like EngineManager or DatabaseManager.

Typical plugin set‑up:

_processors = new ProcessorManager();
_processors.Initialize(this);
_processors.AddProcessor<DamageProcessor>(this);
_processors.Load(this);
_processors.Ready(this);

4. End‑to‑End Example

Step 1: Create a Domain Event

public record DamageEvent(Entity Attacker, Entity Target, float Amount);

Step 2: Implement a Processor

public sealed class PvpFlagProcessor
    : ProcessorBase<DamageEvent, bool?>
{
    public override bool CanProcess(DamageEvent e, bool post)
        => !post && e.Target.IsPlayer && e.Attacker.IsPlayer;

    public override ProcessToken PostProcess(DamageEvent e, out bool? result)
    {
        if (!e.Target.IsPvpEnabled)
        {
            result = false; // block damage
            return ProcessToken.Cancel;
        }

        result = null;
        return ProcessToken.Continue;
    }
}

Step 3: Use the Pipeline

bool ApplyDamage(DamageEvent e)
{
    foreach (var proc in _processors)
    {
        if (!proc.CanProcess(e, isPost:false)) continue;

        var token = proc.PostProcess(e, out bool? allow);
        if (token == ProcessToken.Cancel)  return false;
        if (token == ProcessToken.Skip)    break;
    }

    GameAPI.DealDamage(e.Target, e.Amount);
    return true;
}

5. Integration Patterns

Pattern Description
Validation Run a chain of processors to validate a command; Cancel on first failure.
Modifier Stack Each processor mutates the event/result (e.Amount *= 0.9f), returns Continue.
Analytics Tap Processors with CanProcess(..., post=true) consume post phase events and forward to telemetry services.
Fallback Use DefaultProcessor<T> to attempt multiple strategies until one returns true.

6. Best Practices

  • Keep processors stateless when possible; rely on injected services.
  • Return Skip sparingly—prefer predictable Continue/Cancel.
  • Group related processors under dedicated managers (CombatProcessorManager) to keep lookup sets small.
  • Log decisions at Debug level with Logger.Create<YourProcessor>().
  • Combine with EventBus—publish pipeline outcome as an event for complete decoupling.

7. Troubleshooting

Symptom Possible Cause Resolution
Processors never fire Not added to manager or CanProcess returns false Add during Initialize; validate predicate.
Unexpected Cancel Processor logic bug Enable Trace logging to inspect tokens.
Performance hit Too many processors in hot loop Split managers by domain or memoize expensive checks.

TL;DR

  • Processor = mini‑middleware layer for event pipelines.
  • ProcessToken controls flow (Continue → next, Skip → stop processors, Cancel → abort).
  • ProcessorManager plugs into the same lifecycle as other RisingV managers.
    Use processors to validate, transform, or veto operations without tightly coupling modules.