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.isPost
–false
for pre‑processing,true
for post‑processing.result
– custom output, may benull
.
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 predictableContinue
/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.