Skip to main content

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 from Actor.

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