State<T>
Generic base class for implementing state machine states with strongly-typed actor reference.
Namespace: ControlBee.Models
Implements: IState, IDisposable
Overview
State<T> is the recommended base class for implementing actor states. It provides:
- Strongly-typed actor access via generic type parameter
- Implementation of IState interface for message processing
- Access to TimeManager for timer operations
- Disposable pattern for cleanup
Unlike implementing IState directly, State<T> gives you compile-time type safety when accessing the actor's properties and methods.
Generic Parameter
public abstract class State<T>(T actor) : IState
where T : Actor
Type Parameter:
T— The specific actor type this state belongs to. Must inherit fromActor.
Benefits:
- No casting required when accessing actor
- IntelliSense support for actor members
- Compile-time type checking
- Refactoring safety
Constructor
protected State(T actor)
Parameters:
actor— The actor instance that owns this state
Usage Example:
public class IdleState : State<StageActor>
{
public IdleState(StageActor actor) : base(actor)
{
}
}
Protected Members
Actor
protected T Actor { get; }
The strongly-typed actor instance that owns this state.
Usage Example:
public class ProcessingState : State<StageActor>
{
public ProcessingState(StageActor actor) : base(actor) { }
public override bool ProcessMessage(Message message)
{
if (message.Name == "Start")
{
// Direct access to StageActor properties - no casting needed
Actor.X.TrapezoidalMove(Actor.TargetPos.Value.X, 100, 200, 200);
Actor.Vacuum.TurnOn();
Actor.Busy.Value = true;
return true;
}
return false;
}
}
TimeManager
protected ITimeManager TimeManager { get; }
Convenience property for accessing the time manager. Equivalent to Actor.TimeManager.
Usage Example:
public class TimedState : State<MyActor>
{
public TimedState(MyActor actor) : base(actor) { }
public override bool ProcessMessage(Message message)
{
if (message.Name == "StartOperation")
{
var stopwatch = TimeManager.StartNew();
PerformOperation();
var elapsed = stopwatch.ElapsedMilliseconds;
Logger.Info($"Operation took {elapsed}ms");
return true;
}
return false;
}
}
Abstract Method
ProcessMessage
public abstract bool ProcessMessage(Message message);
Must be implemented by derived states to handle incoming messages.
Returns: true if message was handled, false otherwise.
See: IState.ProcessMessage for detailed documentation.
Virtual Method
Dispose
public virtual void Dispose()
Called when the state is being disposed. Override to perform cleanup.
Default Implementation: Empty (does nothing).
Usage Example:
public class ResourceState : State<MyActor>
{
private Timer? timer;
public ResourceState(MyActor actor) : base(actor)
{
timer = new Timer(OnTimer, null, 1000, 1000);
}
public override bool ProcessMessage(Message message)
{
// ... message handling ...
return false;
}
public override void Dispose()
{
timer?.Dispose();
timer = null;
}
}
Usage Patterns
Simple State
public class IdleState : State<StageActor>
{
public IdleState(StageActor actor) : base(actor) { }
public override bool ProcessMessage(Message message)
{
switch (message.Name)
{
case "Start":
Actor.SetState(new ProcessingState(Actor));
return true;
case "Initialize":
Actor.SetState(new InitializingState(Actor));
return true;
default:
return false;
}
}
}
State with Entry/Exit Logic
public class ProcessingState : State<StageActor>
{
public ProcessingState(StageActor actor) : base(actor)
{
// Entry logic
Actor.Busy.Value = true;
Actor.StatusLight.TurnOn();
Actor.X.Enable = true;
}
public override bool ProcessMessage(Message message)
{
if (message.Name == "Complete")
{
Actor.SetState(new IdleState(Actor));
return true;
}
return false;
}
public override void Dispose()
{
// Exit logic
Actor.Busy.Value = false;
Actor.StatusLight.TurnOff();
Actor.X.Enable = false;
}
}
State with Timer
public class WaitingState : State<StageActor>
{
private readonly Timer timer;
public WaitingState(StageActor actor) : base(actor)
{
// Start periodic check
timer = new Timer(CheckCondition, null, 0, 100);
}
private void CheckCondition(object? state)
{
if (Actor.MaterialSensor.Value)
{
Actor.SetState(new ProcessingState(Actor));
}
}
public override bool ProcessMessage(Message message)
{
if (message.Name == "Cancel")
{
Actor.SetState(new IdleState(Actor));
return true;
}
return false;
}
public override void Dispose()
{
timer.Dispose();
}
}
State with Async Operation
public class MovingState : State<StageActor>
{
private readonly CancellationTokenSource cts;
public MovingState(StageActor actor) : base(actor)
{
cts = new CancellationTokenSource();
StartMove();
}
private async void StartMove()
{
try
{
Actor.X.TrapezoidalMove(Actor.TargetPos.Value.X, 100, 200, 200);
// Wait for completion with timeout
await Task.Run(() => Actor.X.Wait(), cts.Token)
.WaitAsync(TimeSpan.FromSeconds(5), cts.Token);
// Move complete
Actor.Send(new Message(Actor, "MoveComplete"));
}
catch (OperationCanceledException)
{
// Cancelled
}
catch (TimeoutException)
{
Actor.SetState(new ErrorState(Actor, "Move timeout"));
}
}
public override bool ProcessMessage(Message message)
{
if (message.Name == "MoveComplete")
{
Actor.SetState(new IdleState(Actor));
return true;
}
if (message.Name == "Cancel")
{
cts.Cancel();
Actor.X.Stop();
Actor.SetState(new IdleState(Actor));
return true;
}
return false;
}
public override void Dispose()
{
cts.Cancel();
cts.Dispose();
}
}
Best Practices
1. Always Use Generic Type
// ✅ Good - Strongly typed
public class MyState : State<MyActor>
{
public override bool ProcessMessage(Message message)
{
Actor.MyProperty = value; // No casting needed
return true;
}
}
// ❌ Bad - Loses type safety
public class MyState : IState
{
private MyActor _actor;
public bool ProcessMessage(Message message)
{
((MyActor)_actor).MyProperty = value; // Requires casting
return true;
}
}
2. Initialize in Constructor
// ✅ Good - Entry logic in constructor
public ProcessingState(StageActor actor) : base(actor)
{
Actor.Busy.Value = true;
Actor.Motor.Start();
}
// ❌ Bad - Missing initialization
public ProcessingState(StageActor actor) : base(actor)
{
}
3. Cleanup in Dispose
// ✅ Good - Cleanup in Dispose
public override void Dispose()
{
timer?.Dispose();
Actor.Motor.Stop();
Actor.Busy.Value = false;
}
// ❌ Bad - No cleanup
// Resources leak when state changes
4. Return Correct Values
// ✅ Good - Returns false for unhandled messages
public override bool ProcessMessage(Message message)
{
if (message.Name == "KnownMessage")
{
HandleMessage();
return true;
}
return false; // Let other handlers try
}
// ❌ Bad - Always returns true
public override bool ProcessMessage(Message message)
{
// ...
return true; // Blocks message propagation
}
See Also
- IState Interface - State interface definition
- State Management Guide - Complete state patterns guide
- Message Passing Guide - Message handling
- Actor System Guide - Actor development