IState
상태 머신 패턴으로 메시지를 처리하는 액터 상태를 위한 인터페이스입니다. 상태는 액터 내에서 개별 동작 모드를 나타내며, 각각 특정 메시지를 처리하고 다른 상태로 전환할 시기를 결정합니다.
네임스페이스: ControlBee.Interfaces
상속: IDisposable
개요
ControlBee는 메시지 기반 상태 머신을 사용하여 액터 동작을 구성합니다. 각 상태는:
- 독립적으로 메시지를 처리합니다
- 다른 상태로 전환할 시기를 결정합니다
- 진입 및 종료를 위한 수명 주기 후크를 가집니다
- 주기적 또는 지연된 작업을 위한 타이머를 관리할 수 있습니다
- 계층적 동작을 위해 스택될 수 있습니다
상태 머신 패턴:
Idle → WaitingForMaterial → Processing → Unloading → Idle
각 액터는 현재 상태(또는 상태 스택)를 유지하며, 모든 수신 메시지는 활성 상태의 ProcessMessage 메서드로 라우팅됩니다.
메서드
ProcessMessage
이 상태 내에서 수신 메시지를 처리합니다.
bool ProcessMessage(Message message);
매개변수:
message— 이름과 선택적 데이터 페이로드를 포함하는 처리할 메시지입니다.
반환값: 이 상태에서 메시지를 처리한 경우 true, 메시지가 관련이 없어 부모 상태로 전파되어야 하는 경우 false입니다.
설명:
상태 인터페이스의 핵심 메서드입니다. 메시지가 액터로 전송되면 현재 상태의 ProcessMessage 메서드로 전달됩니다. 상태는 메시지를 검사하고 다음 중 하나를 수행합니다:
- 처리 - 메시지를 처리하고
true를 반환합니다 - 무시 -
false를 반환하여 메시지가 스택된 부모 상태로 전파되도록 허용합니다
사용 패턴:
public override bool ProcessMessage(Message msg)
{
switch (msg.Name)
{
case "Start":
// 메시지 처리
DoSomething();
return true; // 메시지 처리됨
case "Stop":
// 처리 및 전환
SetState(new IdleState(_actor));
return true; // 메시지 처리됨
default:
return false; // 처리되지 않음, 전파될 수 있음
}
}
State 클래스
실제로는 IState를 구현하고 추가 기능을 제공하는 State 기본 클래스를 상속합니다:
public class MyState : State
{
private MyActor Actor => (MyActor)_actor;
public MyState(Actor actor) : base(actor)
{
}
// 수명 주기 메서드
public override void OnEntry(Message msg) { }
public override bool OnProcess(Message msg) { return false; }
public override void OnExit(Message msg) { }
}
생성자
public MyState(Actor actor) : base(actor)
모든 상태는 소유 액터에 대한 참조를 받아야 합니다. 강력한 타입의 액터 참조를 저장합니다:
private MyActor Actor => (MyActor)_actor;
수명 주기 메서드
OnEntry
이 상태로 전환할 때 한 번 호출됩니다.
public override void OnEntry(Message msg)
{
// 상태 초기화
Actor.Busy.Value = true;
Actor.StatusLight.TurnOn();
// 작업 시작
Actor.Motor.Start();
// 타이머 설정
StartTimer("Timeout", 5000); // 5초 타임아웃
}
사용 사례:
- 상태별 리소스 초기화
- 하드웨어 작업 시작
- 타이머 설정
- 상태 변수 업데이트
- 상태 전환 로그
OnProcess
이 상태에 있는 동안 모든 메시지에 대해 호출됩니다.
public override bool OnProcess(Message msg)
{
switch (msg.Name)
{
case "SensorDetected":
HandleSensor((bool)msg.Data);
return true;
case "TimerMessage":
HandleTimeout();
return true;
case "Stop":
SetState(new IdleState(_actor));
return true;
default:
return false; // 처리되지 않음
}
}
반환값:
true- 이 상태에서 메시지를 처리했습니다false- 메시지가 관련이 없습니다, 전파를 허용합니다
사용 사례:
- 수신 메시지 처리
- 센서 이벤트 처리
- 타이머 만료에 응답
- 상태 전환 관리
- 다른 액터와 조정
OnExit
이 상태를 떠날 때 한 번 호출됩니다.
public override void OnExit(Message msg)
{
// 상태 정리
Actor.Busy.Value = false;
Actor.StatusLight.TurnOff();
// 작업 중지
Actor.Motor.Stop();
// 타이머 취소
CancelAllTimers();
// 리소스 해제
Actor.Vacuum.TurnOff();
}
사용 사례:
- 상태별 리소스 정리
- 하드웨어 작업 중지
- 타이머 취소
- 상태 변수 업데이트
- 상태 데이터 저장
중요: 리소스 누수를 방지하고 적절한 상태 전환을 보장하려면 항상 OnExit에서 정리하세요.
상태 전환 메서드
SetState
현재 상태를 완전히 교체합니다:
public override bool OnProcess(Message msg)
{
if (msg.Name == "Start")
{
SetState(new ProcessingState(_actor));
return true;
}
return false;
}
흐름:
- 현재 상태의
OnExit()호출 - 새 상태의
OnEntry()호출 - 액터가 이제 새 상태에 있음
사용 시기:
- 정상적인 상태 진행
- 상태가 완전히 완료됨
- 현재 상태로 돌아갈 필요가 없음
PushState
스택에 새 상태를 푸시합니다(현재 상태 일시 중지):
public override bool OnProcess(Message msg)
{
if (msg.Name == "EmergencyStop")
{
PushState(new EmergencyState(_actor));
return true;
}
return false;
}
흐름:
- 새 상태의
OnEntry()호출 - 현재 상태 일시 중단(OnExit이 호출되지 않음)
- 메시지가 새 상태로 이동
사용 시기:
- 임시 중단
- 나중에 현재 상태로 돌아가야 함
- 계층적 동작(기본 상태 + 오버레이)
PopState
스택의 이전 상태로 돌아갑니다:
public override bool OnProcess(Message msg)
{
if (msg.Name == "Resume")
{
PopState();
return true;
}
return false;
}
흐름:
- 현재 상태의
OnExit()호출 - 이전 상태 재개(OnEntry가 다시 호출되지 않음)
- 메시지가 이전 상태로 이동
사용 시기:
- 임시 중단 완료
- 하위 작업에서 돌아옴
- 오버레이 동작 완료
타이머 관리
상태는 주기적 또는 지연된 작업을 위한 타이머를 관리할 수 있습니다:
StartTimer
StartTimer("CheckSensor", 100); // 100ms 후 발동
지정된 간격 후에 TimerMessage를 보낼 타이머를 시작합니다.
CancelTimer
CancelTimer("CheckSensor");
이름으로 특정 타이머를 취소합니다.
CancelAllTimers
CancelAllTimers();
이 상태에서 시작한 모든 타이머를 취소합니다. 일반적으로 OnExit()에서 호출됩니다.
타이머 패턴:
public override void OnEntry(Message msg)
{
// 주기적 확인 시작
StartTimer("CheckSensor", 100);
}
public override bool OnProcess(Message msg)
{
if (msg.Name == "TimerMessage" && (string)msg.Data == "CheckSensor")
{
if (Actor.MaterialSensor.Value)
{
// 재료 감지됨, 전환
SetState(new ProcessingState(_actor));
}
else
{
// 계속 확인
StartTimer("CheckSensor", 100);
}
return true;
}
return false;
}
public override void OnExit(Message msg)
{
CancelAllTimers();
}
내장 메시지
StateEntryMessage
상태 진입 시 자동으로 전송됩니다:
public override bool OnProcess(Message msg)
{
if (msg.Name == "StateEntryMessage")
{
// 메시지 컨텍스트가 필요한 초기화 수행
return true;
}
return false;
}
참고: 일반적으로 대신 OnEntry()에서 초기화가 수행됩니다.
StateExitMessage
상태를 떠나기 전에 자동으로 전송됩니다:
public override bool OnProcess(Message msg)
{
if (msg.Name == "StateExitMessage")
{
// 떠나기 전에 상태 저장
Actor.LastPosition.Value = Actor.X.CommandPosition;
return true;
}
return false;
}
참고: 일반적으로 대신 OnExit()에서 정리가 수행됩니다.
TimerMessage
타이머가 만료될 때 전송됩니다:
if (msg.Name == "TimerMessage" && (string)msg.Data == "MyTimer")
{
// 타이머 만료 처리
return true;
}
일반적인 상태 패턴
1. 단순 상태
하나의 책임, 명확한 전환:
public class IdleState : State
{
public IdleState(Actor actor) : base(actor) { }
public override void OnEntry(Message msg)
{
Console.WriteLine("준비됨");
}
public override bool OnProcess(Message msg)
{
if (msg.Name == "Start")
{
SetState(new ProcessingState(_actor));
return true;
}
return false;
}
}
2. 대기 상태
조건이 충족될 때까지 폴링:
public class WaitingState : State
{
public WaitingState(Actor actor) : base(actor) { }
public override void OnEntry(Message msg)
{
StartTimer("CheckCondition", 100);
}
public override bool OnProcess(Message msg)
{
if (msg.Name == "TimerMessage")
{
var actor = (MyActor)_actor;
if (actor.Sensor.Value)
{
SetState(new NextState(_actor));
}
else
{
StartTimer("CheckCondition", 100);
}
return true;
}
return false;
}
public override void OnExit(Message msg)
{
CancelAllTimers();
}
}
3. 비동기 작업 상태
비동기 작업을 시작하고 완료를 기다립니다:
public class MovingState : State
{
public MovingState(Actor actor) : base(actor) { }
public override void OnEntry(Message msg)
{
var actor = (MyActor)_actor;
actor.X.TrapezoidalMove(100, 50, 100, 100);
StartTimer("MoveTimeout", 5000);
}
public override bool OnProcess(Message msg)
{
if (msg.Name == "MoveComplete")
{
CancelTimer("MoveTimeout");
SetState(new NextState(_actor));
return true;
}
if (msg.Name == "TimerMessage")
{
SetState(new ErrorState(_actor, "이동 타임아웃"));
return true;
}
return false;
}
public override void OnExit(Message msg)
{
CancelAllTimers();
}
}
4. 루핑 상태
여러 항목을 순차적으로 처리합니다:
public class ProcessingState : State
{
private int currentIndex = 0;
public ProcessingState(Actor actor) : base(actor) { }
public override void OnEntry(Message msg)
{
currentIndex = 0;
ProcessNext();
}
private void ProcessNext()
{
var actor = (MyActor)_actor;
if (currentIndex >= actor.TotalCount)
{
SetState(new DoneState(_actor));
return;
}
// 현재 항목 처리
actor.ProcessItem(currentIndex);
}
public override bool OnProcess(Message msg)
{
if (msg.Name == "ItemComplete")
{
currentIndex++;
ProcessNext();
return true;
}
return false;
}
}
5. 오류 처리 상태
오류를 처리하고 복구를 제공합니다:
public class ErrorState : State
{
private string errorMessage;
public ErrorState(Actor actor, string error) : base(actor)
{
errorMessage = error;
}
public override void OnEntry(Message msg)
{
var actor = (MyActor)_actor;
// 모든 모션 중지
actor.X.Stop();
// 사용자에게 알림
Console.WriteLine($"오류: {errorMessage}");
actor.ErrorLight.TurnOn();
}
public override bool OnProcess(Message msg)
{
if (msg.Name == "Reset")
{
SetState(new IdleState(_actor));
return true;
}
if (msg.Name == "Retry")
{
SetState(new InitializingState(_actor));
return true;
}
return false;
}
public override void OnExit(Message msg)
{
var actor = (MyActor)_actor;
actor.ErrorLight.TurnOff();
}
}
메시지 전파
상태 스택(PushState)을 사용할 때 처리되지 않은 메시지는 부모 상태로 전파됩니다:
// 상태 스택: [BaseState, OverlayState]
// 현재: OverlayState
public class OverlayState : State
{
public override bool OnProcess(Message msg)
{
if (msg.Name == "OverlaySpecific")
{
// 오버레이 특정 메시지 처리
return true;
}
// false 반환 - 메시지가 BaseState로 전파됨
return false;
}
}
public class BaseState : State
{
public override bool OnProcess(Message msg)
{
if (msg.Name == "EmergencyStop")
{
// OverlayState가 활성화되어 있어도 긴급 상황 처리
SetState(new EmergencyState(_actor));
return true;
}
return false;
}
}
모범 사례
1. 상태당 하나의 책임
// ✅ 좋음 - 각 상태는 명확한 목적을 가짐
public class MovingState : State { }
public class ProcessingState : State { }
public class WaitingState : State { }
// ❌ 나쁨 - 상태가 너무 많은 일을 함
public class DoEverythingState : State { }
2. OnExit에서 항상 정리
// ✅ 좋음
public override void OnEntry(Message msg)
{
StartTimer("Monitor", 100);
Actor.Motor.Start();
}
public override void OnExit(Message msg)
{
CancelAllTimers();
Actor.Motor.Stop();
}
// ❌ 나쁨 - 정리 없음
public override void OnEntry(Message msg)
{
StartTimer("Monitor", 100);
Actor.Motor.Start();
}
// 모터가 계속 켜져 있고, 타이머가 계속 실행됨!
3. 설명적인 이름 사용
public class WaitingForVacuumState : State { } // ✅ 명확함
public class MovingToLoadPositionState : State { } // ✅ 명확함
public class State1 : State { } // ❌ 불명확함
4. 타임아웃 처리
// ✅ 좋음 - 타임아웃이 중단 방지
public override void OnEntry(Message msg)
{
Actor.X.MoveAsync(100);
StartTimer("MoveTimeout", 5000);
}
public override bool OnProcess(Message msg)
{
if (msg.Name == "TimerMessage")
{
SetState(new ErrorState(_actor, "타임아웃"));
return true;
}
return false;
}
5. 중단에 상태 스택 사용
// ✅ 좋음 - 컨텍스트 보존
if (msg.Name == "PauseForInspection")
{
PushState(new InspectionState(_actor));
// 현재 상태로 돌아갈 수 있음
}
// ❌ 나쁨 - 컨텍스트 손실
if (msg.Name == "PauseForInspection")
{
SetState(new InspectionState(_actor));
// 어디에 있었는지 손실됨
}
참고
- 상태 관리 가이드 - 상태 패턴 완벽 가이드
- 메시지 전달 가이드 - 액터 통신
- 액터 시스템 가이드 - 액터 개발 기본 사항