C #의 간단한 상태 머신 예제?


257

최신 정보:

예제에 다시 한 번 감사 드리며, 그들은 매우 도움이되었으며 다음과 같이 나는 그들로부터 아무것도 빼앗지 않을 것입니다.

내가 제시 한 예와 상태 머신을 이해하는 한, 상태 머신이 일반적으로 이해하는 것의 절반 만 현재 주어진 예제가 아닙니까?
예제는 상태를 변경하지만 변수 값을 변경하고 다른 상태에서 다른 값 변경을 허용하는 것으로 만 표현되는 반면, 일반적으로 상태 시스템은 동작을 변경하지 않아야합니다. 상태에 따라 변수에 대해 다른 값 변경을 허용한다는 의미이지만 다른 상태에 대해 다른 방법을 실행할 수 있다는 의미입니다.

또는 상태 시스템과 일반적인 용도에 대한 오해가 있습니까?

친애하는


원래 질문 :

C #의 상태 시스템 및 반복자 블록 과 상태 시스템을 만드는 도구 및 C #을위한 도구 대한이 토론을 찾았 으므로 많은 추상적 인 내용을 찾았지만 멍청한 놈에게는 약간 혼란 스럽습니다.

따라서 누군가가 3,4 개의 상태로 간단한 상태 머신을 실현하는 C # 소스 코드 예제를 제공 할 수 있다면 좋을 것입니다.



일반적인 상태 머신이나 반복자 기반 머신에 대해 궁금하십니까?
Skurmedel

2
예, DAG를 daigram 등으로 닷넷 코어 태형 lib에 있습니다 - 가치 검토가 : hanselman.com/blog/...
zmische

답변:


416

이 간단한 상태 다이어그램으로 시작하겠습니다.

간단한 상태 머신 다이어그램

우리는 :

  • 4 가지 상태 (비활성, 활성, 일시 중지 및 종료)
  • 5 가지 유형의 상태 전환 (시작 명령, 종료 명령, 일시 중지 명령, 재개 명령, 종료 명령).

현재 상태 및 명령에 대해 switch 문을 수행하거나 전이 테이블에서 전이를 찾는 등의 몇 가지 방법으로이를 C #으로 변환 할 수 있습니다. 이 간단한 상태 머신의 경우 다음을 사용하여 표현하기 매우 쉬운 전이 테이블을 선호합니다 Dictionary.

using System;
using System.Collections.Generic;

namespace Juliet
{
    public enum ProcessState
    {
        Inactive,
        Active,
        Paused,
        Terminated
    }

    public enum Command
    {
        Begin,
        End,
        Pause,
        Resume,
        Exit
    }

    public class Process
    {
        class StateTransition
        {
            readonly ProcessState CurrentState;
            readonly Command Command;

            public StateTransition(ProcessState currentState, Command command)
            {
                CurrentState = currentState;
                Command = command;
            }

            public override int GetHashCode()
            {
                return 17 + 31 * CurrentState.GetHashCode() + 31 * Command.GetHashCode();
            }

            public override bool Equals(object obj)
            {
                StateTransition other = obj as StateTransition;
                return other != null && this.CurrentState == other.CurrentState && this.Command == other.Command;
            }
        }

        Dictionary<StateTransition, ProcessState> transitions;
        public ProcessState CurrentState { get; private set; }

        public Process()
        {
            CurrentState = ProcessState.Inactive;
            transitions = new Dictionary<StateTransition, ProcessState>
            {
                { new StateTransition(ProcessState.Inactive, Command.Exit), ProcessState.Terminated },
                { new StateTransition(ProcessState.Inactive, Command.Begin), ProcessState.Active },
                { new StateTransition(ProcessState.Active, Command.End), ProcessState.Inactive },
                { new StateTransition(ProcessState.Active, Command.Pause), ProcessState.Paused },
                { new StateTransition(ProcessState.Paused, Command.End), ProcessState.Inactive },
                { new StateTransition(ProcessState.Paused, Command.Resume), ProcessState.Active }
            };
        }

        public ProcessState GetNext(Command command)
        {
            StateTransition transition = new StateTransition(CurrentState, command);
            ProcessState nextState;
            if (!transitions.TryGetValue(transition, out nextState))
                throw new Exception("Invalid transition: " + CurrentState + " -> " + command);
            return nextState;
        }

        public ProcessState MoveNext(Command command)
        {
            CurrentState = GetNext(command);
            return CurrentState;
        }
    }


    public class Program
    {
        static void Main(string[] args)
        {
            Process p = new Process();
            Console.WriteLine("Current State = " + p.CurrentState);
            Console.WriteLine("Command.Begin: Current State = " + p.MoveNext(Command.Begin));
            Console.WriteLine("Command.Pause: Current State = " + p.MoveNext(Command.Pause));
            Console.WriteLine("Command.End: Current State = " + p.MoveNext(Command.End));
            Console.WriteLine("Command.Exit: Current State = " + p.MoveNext(Command.Exit));
            Console.ReadLine();
        }
    }
}

개인적인 취향의 문제로서, 나는 내 상태 머신을 설계하고자 GetNext다음 상태를 반환하는 기능을 결정적으로 하고, MoveNext상태 머신을 변이하는 기능.


65
GetHashCode()소수를 올바르게 사용 하려면 +1하십시오 .
ja72

13
GetHashCode ()의 목적을 설명해 주시겠습니까?
Siddharth

14
@Siddharth : StateTransition클래스는 사전에서 키로 사용되며 키의 동등성이 중요합니다. 두 개의 별개의 인스턴스 StateTransition와 동일한만큼 그들은 같은 전이 표현 (예를 고려한다 CurrentStateCommand동일하다). 평등을 구현하려면 오버라이드 (override) 할 필요가 Equals아니라으로 GetHashCode. 특히 사전은 해시 코드를 사용하며 두 개의 동일한 객체는 동일한 해시 코드를 반환해야합니다. 동일하지 않은 많은 객체가 동일한 해시 코드를 공유하지 않으면 성능이 향상됩니다 GetHashCode.
Martin Liversage

14
이것이 분명히 상태 머신 (및 적절한 C # 구현 구현)을 얻는 동안 여전히 동작 변경에 대한 OP의 질문에 대한 답변이 여전히 누락되었다고 생각합니까? 결국 상태를 계산하지만 프로그램의 실제 핵심 요소이며 일반적으로 진입 / 종료 이벤트라고하는 상태 변경과 관련된 동작이 여전히 누락되었습니다.
stijn

2
누군가 필요하다면 : 나는이 테이트 머신을 조정하여 통일 게임에서 사용했습니다. git hub에서 사용할 수 있습니다 : github.com/MarcoMig/Finite-State-Machine-FSM
Max_Power89

73

기존 오픈 소스 Finite State Machine 중 하나를 사용할 수 있습니다. 예를 들어 bbv.Common.StateMachine은 http://code.google.com/p/bbvcommon/wiki/StateMachine에 있습니다. 매우 직관적 인 유창한 구문과 진입 / 종료 동작, 전환 동작, 가드, 계층 적, 수동적 구현 (호출자의 스레드에서 실행) 및 활성 구현 (FSM이 실행되는 자체 스레드, 이벤트가 대기열에 추가됩니다).

Juliets를 예로 들어 상태 머신에 대한 정의는 매우 쉽습니다.

var fsm = new PassiveStateMachine<ProcessState, Command>();
fsm.In(ProcessState.Inactive)
   .On(Command.Exit).Goto(ProcessState.Terminated).Execute(SomeTransitionAction)
   .On(Command.Begin).Goto(ProcessState.Active);
fsm.In(ProcessState.Active)
   .ExecuteOnEntry(SomeEntryAction)
   .ExecuteOnExit(SomeExitAction)
   .On(Command.End).Goto(ProcessState.Inactive)
   .On(Command.Pause).Goto(ProcessState.Paused);
fsm.In(ProcessState.Paused)
   .On(Command.End).Goto(ProcessState.Inactive).OnlyIf(SomeGuard)
   .On(Command.Resume).Goto(ProcessState.Active);
fsm.Initialize(ProcessState.Inactive);
fsm.Start();

fsm.Fire(Command.Begin);

업데이트 : 프로젝트 위치가 https://github.com/appccelerate/statemachine으로 이동했습니다.


4
이 훌륭한 오픈 소스 상태 머신을 참조 해 주셔서 감사합니다. 현재 상태를 어떻게 알 수 있습니까?
Ramazan Polat

2
당신은 할 수 없어요. 상태가 불안정합니다. 상태를 요청하면 전환 중일 수 있습니다. 모든 작업은 전환, 상태 입력 및 상태 종료 내에서 수행해야합니다. 실제로 상태를 원하면 로컬 필드를 추가하고 입력 조치에서 상태를 지정할 수 있습니다.
Remo Gloor

4
문제는 무엇을 "필요"하고 SM 상태 또는 다른 종류의 상태가 정말로 필요한지에 관한 것입니다. 예를 들어, 일부 표시 텍스트가 필요한 경우 전송 준비에 여러 하위 상태가있는 경우와 같이 일부 표시 텍스트가 동일한 표시 텍스트를 가질 수 있습니다. 이 경우에는 의도 한대로 정확하게 수행해야합니다. 올바른 위치에서 일부 표시 텍스트를 업데이트하십시오. 예를 들어 ExecuteOnEntry 내에서. 더 많은 정보가 필요하면 새로운 질문을하고 여기에서 주제를 벗어나기 때문에 문제를 정확하게 설명하십시오.
Remo Gloor

Ok 새로운 질문을하고 답변을 기다리고 있습니다. 나는 당신이 가장 좋은 대답을 가지고 있지만 여전히 질문자가 받아들이지 않았기 때문에 다른 사람 이이 문제를 해결하지 못한다고 생각하기 때문에. 여기에 질문 URL을 게시하겠습니다. 감사.
Ramazan Polat

4
유창하고 선언적인 API의 경우 +1 그것은 굉장. BTW, Google 코드가 오래된 것 같습니다. 그들의 최신 프로젝트 사이트는 여기
KFL

51

다음은 매우 고전적인 유한 상태 기계의 예입니다 (TV와 같은 매우 단순화 된 전자 장치 모델링).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace fsm
{
class Program
{
    static void Main(string[] args)
    {
        var fsm = new FiniteStateMachine();
        Console.WriteLine(fsm.State);
        fsm.ProcessEvent(FiniteStateMachine.Events.PlugIn);
        Console.WriteLine(fsm.State);
        fsm.ProcessEvent(FiniteStateMachine.Events.TurnOn);
        Console.WriteLine(fsm.State);
        fsm.ProcessEvent(FiniteStateMachine.Events.TurnOff);
        Console.WriteLine(fsm.State);
        fsm.ProcessEvent(FiniteStateMachine.Events.TurnOn);
        Console.WriteLine(fsm.State);
        fsm.ProcessEvent(FiniteStateMachine.Events.RemovePower);
        Console.WriteLine(fsm.State);
        Console.ReadKey();
    }

    class FiniteStateMachine
    {
        public enum States { Start, Standby, On };
        public States State { get; set; }

        public enum Events { PlugIn, TurnOn, TurnOff, RemovePower };

        private Action[,] fsm;

        public FiniteStateMachine()
        {
            this.fsm = new Action[3, 4] { 
                //PlugIn,       TurnOn,                 TurnOff,            RemovePower
                {this.PowerOn,  null,                   null,               null},              //start
                {null,          this.StandbyWhenOff,    null,               this.PowerOff},     //standby
                {null,          null,                   this.StandbyWhenOn, this.PowerOff} };   //on
        }
        public void ProcessEvent(Events theEvent)
        {
            this.fsm[(int)this.State, (int)theEvent].Invoke();
        }

        private void PowerOn() { this.State = States.Standby; }
        private void PowerOff() { this.State = States.Start; }
        private void StandbyWhenOn() { this.State = States.Standby; }
        private void StandbyWhenOff() { this.State = States.On; }
    }
}
}

6
스테이트 머신을 처음 사용하는 사람이라면 누구나 먼저 발을 젖게하는 훌륭한 첫 번째 예입니다.
PositiveGuy

2
나는 기계 상태를 처음 접했고 진지하게 이것이 빛을 가져 왔습니다. 감사합니다!
MC5

1
나는이 구현을 좋아했다. 이것에 걸려 넘어 질지도 모르는 사람에게는 약간의 "개선"이 있습니다. FSM 클래스에서 private void DoNothing() {return;}모든 null 인스턴스를 추가 하고로 바꿨습니다 this.DoNothing. 현재 상태를 반환하는 유쾌한 부작용이 있습니다.
Sethmo011

1
이 이름들 뒤에 추론이 있는지 궁금합니다. 이것을 보면 첫 번째 직관은의 요소 이름을로 변경하는 StatesUnpowered, Standby, On입니다. 내 추론은 누군가 내 텔레비전의 상태를 물어 보면 "시작"이 아니라 "끄기"라고 말할 것입니다. 나는 또한 변화 StandbyWhenOnStandbyWhenOffTurnOnTurnOff. 그렇게하면 코드를보다 직관적으로 읽을 수 있지만 용어를 덜 적합하게 만드는 규칙이나 다른 요소가 있는지 궁금합니다.
Jason Hamje

합리적으로 보입니다. 저는 실제로 어떤 국가 명명 규칙을 따르지 않았습니다. 어떤 모델이든 의미가있는 이름입니다.
Pete Stensønes

20

여기에는 뻔뻔한 자체 프로모션이 있지만, 얼마 전에 YieldMachine 이라는 라이브러리를 만들었습니다 . 예를 들어 램프를 고려하십시오.

램프의 상태 기계

이 상태 머신에는 2 개의 트리거와 3 개의 상태가 있습니다. YieldMachine 코드에서는 모든 상태 관련 동작에 대한 단일 메소드를 작성합니다 goto. 여기서 각 상태에 대해 끔찍한 사용 을 약속합니다 . 트리거는이라는 속성으로 Action장식 된 유형의 속성 또는 필드가됩니다 Trigger. 나는 첫 번째 상태 코드와 그 전환에 대해 다음과 같이 언급했다. 다음 상태는 동일한 패턴을 따릅니다.

public class Lamp : StateMachine
{
    // Triggers (or events, or actions, whatever) that our
    // state machine understands.
    [Trigger]
    public readonly Action PressSwitch;

    [Trigger]
    public readonly Action GotError;

    // Actual state machine logic
    protected override IEnumerable WalkStates()
    {
    off:                                       
        Console.WriteLine("off.");
        yield return null;

        if (Trigger == PressSwitch) goto on;
        InvalidTrigger();

    on:
        Console.WriteLine("*shiiine!*");
        yield return null;

        if (Trigger == GotError) goto error;
        if (Trigger == PressSwitch) goto off;
        InvalidTrigger();

    error:
        Console.WriteLine("-err-");
        yield return null;

        if (Trigger == PressSwitch) goto off;
        InvalidTrigger();
    }
}

짧고 좋은, 어!

이 상태 머신은 단순히 트리거를 전송하여 제어됩니다.

var sm = new Lamp();
sm.PressSwitch(); //go on
sm.PressSwitch(); //go off

sm.PressSwitch(); //go on
sm.GotError();    //get error
sm.PressSwitch(); //go off

명확히하기 위해, 이것을 사용하는 방법을 이해하는 데 도움이되는 몇 가지 의견을 첫 번째 주에 추가했습니다.

    protected override IEnumerable WalkStates()
    {
    off:                                       // Each goto label is a state

        Console.WriteLine("off.");             // State entry actions

        yield return null;                     // This means "Wait until a 
                                               // trigger is called"

                                               // Ah, we got triggered! 
                                               //   perform state exit actions 
                                               //   (none, in this case)

        if (Trigger == PressSwitch) goto on;   // Transitions go here: 
                                               // depending on the trigger 
                                               // that was called, go to
                                               // the right state

        InvalidTrigger();                      // Throw exception on 
                                               // invalid trigger

        ...

이것은 C # 컴파일러가 실제로 사용하는 각 메소드에 대해 내부적으로 상태 머신을 작성했기 때문에 작동합니다. yield return . 이 구문은 일반적으로 데이터 시퀀스를 느리게 생성하는 데 사용되지만이 경우 실제로 반환 된 시퀀스 (모두 null 임)에 관심이 없지만 후드 아래에서 생성되는 상태 동작에 실제로 관심이 있습니다.

StateMachine기본 클래스는 각각 할당 코드 건설에 일부 반사하지 [Trigger]을 설정 조치를,Trigger 부재를 전진 상태 머신을 이동.

그러나 당신은 그것을 사용하기 위해 실제로 내부를 이해할 필요가 없습니다.


2
"goto"는 메소드간에 점프하는 경우에만 끔찍합니다. 다행히도 C #에서는 허용되지 않습니다.
Brannon

좋은 지적! 사실, 정적으로 유형이 지정된 언어가 goto메소드 간 을 허용 할 수 있다면 매우 인상적 입니다.
skrebbel

3
@Brannon : 어떤 언어 goto가 메소드 사이를 뛰어 넘을 수 있습니까? 어떻게 작동하는지 알 수 없습니다. 아니요, goto절차 적 프로그래밍 (단위 자체 테스트와 같이 좋은 점을 복잡하게 함)을 초래하고 코드 반복을 촉진하고 ( InvalidTrigger모든 상태에 어떻게 삽입 해야하는지 알 수 있습니까?) 결국 프로그램 흐름을 따르기가 더 어려워지기 때문에 문제가됩니다. 이것을이 스레드의 (대부분) 다른 솔루션과 비교하면 이것이 전체 FSM이 단일 방법으로 발생하는 유일한 솔루션임을 알 수 있습니다. 보통 우려를 제기하기에 충분합니다.
Groo

1
예를 들어 @Groo, GW-BASIC. 메서드 나 함수가없는 데 도움이됩니다. 그 외에도이 예제에서 "프로그램 흐름을 따르기가 더 어렵다"는 이유를 이해하기가 매우 어렵습니다. 상태 머신입니다. 다른 상태에서 "가는"것이 유일한 방법입니다. 이것은 goto꽤 잘 매핑됩니다 .
skrebbel

3
GW-BASIC은 goto기능 사이를 이동할 수 있지만 기능을 지원하지 않습니까? :) 당신이 옳습니다. "따르기 더 어렵다"는 말은 더 일반적인 goto문제이며,이 경우에는 그다지 큰 문제가 아닙니다.
Groo

13

코드 블록을 오케스트레이션 방식으로 실행할 수있는 반복자 블록을 코딩 할 수 있습니다. 코드 블록이 어떻게 분해되는지는 실제로 어떤 것에도 대응할 필요가 없으며, 코드를 코딩하려는 방식 일뿐입니다. 예를 들면 다음과 같습니다.

IEnumerable<int> CountToTen()
{
    System.Console.WriteLine("1");
    yield return 0;
    System.Console.WriteLine("2");
    System.Console.WriteLine("3");
    System.Console.WriteLine("4");
    yield return 0;
    System.Console.WriteLine("5");
    System.Console.WriteLine("6");
    System.Console.WriteLine("7");
    yield return 0;
    System.Console.WriteLine("8");
    yield return 0;
    System.Console.WriteLine("9");
    System.Console.WriteLine("10");
}

이 경우 CountToTen을 호출하면 실제로 아무것도 실행되지 않습니다. 효과적으로 얻을 수있는 상태 머신 생성기는 상태 머신의 새 인스턴스를 만들 수 있습니다. GetEnumerator ()를 호출하여이 작업을 수행합니다. 결과 IEnumerator는 사실상 MoveNext (...)를 호출하여 구동 할 수있는 상태 머신입니다.

따라서이 예에서는 MoveNext (...)를 처음 호출하면 콘솔에 "1"이 작성되고 다음에 MoveNext (...)를 호출하면 2, 3, 4가 표시됩니다. 보시다시피, 일이 어떻게 발생해야 하는지를 조정하는 데 유용한 메커니즘입니다.



8

이것은 다른 관점에서 상태 머신이기 때문에 여기에 다른 답변을 게시하고 있습니다. 매우 시각적입니다.

내 원래의 대답은 고전적인 명령 코드입니다. 상태 머신을 간단하게 시각화하는 배열로 인해 코드가 진행되는 것처럼 보입니다. 단점은이 모든 것을 작성해야한다는 것입니다. Remos 의 답변은 보일러 플레이트 코드를 작성하는 노력을 덜어 주지만 훨씬 덜 시각적입니다. 세 번째 대안이 있습니다. 실제로 상태 머신을 그립니다.

.NET을 사용하고 런타임 버전 4를 대상으로 할 수있는 경우 워크 플로우의 상태 머신 활동 을 사용하는 옵션이 있습니다. 이것들은 본질적으로 상태 머신 ( 줄리엣 의 다이어그램 에서와 같이 )을 그리고 WF 런타임이 당신을 위해 그것을 실행하게합니다.

자세한 내용은 MSDN 문서 Windows Workflow Foundation 을 사용 하여 State Machines 빌드 및 최신 버전에 대한 CodePlex 사이트 를 참조하십시오.

.NET을 타겟팅 할 때 항상 프로그래머가 아닌 사람들에게 쉽게보고 변경하고 설명 할 수 있기 때문에 선호하는 옵션입니다. 그들이 말하는대로 그림은 천 단어의 가치가있다!


스테이트 머신은 전체 워크 플로 기초의 가장 좋은 부분 중 하나라고 생각합니다!
fabsenet

7

상태 머신은 추상화이며,이를 생성하기 위해 특정 도구가 필요하지 않지만 도구가 유용 할 수 있음을 기억하는 것이 유용합니다.

예를 들어 함수가있는 상태 머신을 실현할 수 있습니다.

void Hunt(IList<Gull> gulls)
{
    if (gulls.Empty())
       return;

    var target = gulls.First();
    TargetAcquired(target, gulls);
}

void TargetAcquired(Gull target, IList<Gull> gulls)
{
    var balloon = new WaterBalloon(weightKg: 20);

    this.Cannon.Fire(balloon);

    if (balloon.Hit)
    {
       TargetHit(target, gulls);
    }
    else
       TargetMissed(target, gulls);
}

void TargetHit(Gull target, IList<Gull> gulls)
{
    Console.WriteLine("Suck on it {0}!", target.Name);
    Hunt(gulls);
}

void TargetMissed(Gull target, IList<Gull> gulls)
{
    Console.WriteLine("I'll get ya!");
    TargetAcquired(target, gulls);
}

이 기계는 갈매기를 찾아 물 풍선으로 때리려 고합니다. 만약 놓치면 맞을 때까지 발사를 시도합니다 (실제로 기대할 수 있습니다). 그렇지 않으면 콘솔에서 화를냅니다. 갈매기가 괴롭힐 때까지 사냥을 계속합니다.

각 기능은 각 상태에 해당합니다. 시작 및 종료 (또는 수락 ) 상태는 표시되지 않습니다. 함수에 의해 모델링 된 것보다 더 많은 상태가있을 수 있습니다. 예를 들어 풍선을 발사 한 후 기계는 실제로 이전과 다른 상태에 있지만이 구별은 비실용적이라고 결정했습니다.

일반적인 방법은 클래스를 사용하여 상태를 표시 한 다음 다른 방식으로 연결하는 것입니다.


7

이 훌륭한 튜토리얼을 온라인에서 찾았으며 유한 상태 머신을 둘러 보았습니다.

http://gamedevelopment.tutsplus.com/tutorials/finite-state-machines-theory-and-implementation--gamedev-11867

이 자습서는 언어에 구애받지 않으므로 C # 요구에 쉽게 맞출 수 있습니다.

또한 사용 된 예 (음식을 찾는 개미)는 이해하기 쉽습니다.


튜토리얼에서 :

여기에 이미지 설명을 입력하십시오

public class FSM {
    private var activeState :Function; // points to the currently active state function

    public function FSM() {
    }

    public function setState(state :Function) :void {
        activeState = state;
    }

    public function update() :void {
        if (activeState != null) {
            activeState();
        }
    }
}


public class Ant
{
    public var position   :Vector3D;
    public var velocity   :Vector3D;
    public var brain      :FSM;

    public function Ant(posX :Number, posY :Number) {
        position    = new Vector3D(posX, posY);
        velocity    = new Vector3D( -1, -1);
        brain       = new FSM();

        // Tell the brain to start looking for the leaf.
        brain.setState(findLeaf);
    }

    /**
    * The "findLeaf" state.
    * It makes the ant move towards the leaf.
    */
    public function findLeaf() :void {
        // Move the ant towards the leaf.
        velocity = new Vector3D(Game.instance.leaf.x - position.x, Game.instance.leaf.y - position.y);

        if (distance(Game.instance.leaf, this) <= 10) {
            // The ant is extremelly close to the leaf, it's time
            // to go home.
            brain.setState(goHome);
        }

        if (distance(Game.mouse, this) <= MOUSE_THREAT_RADIUS) {
            // Mouse cursor is threatening us. Let's run away!
            // It will make the brain start calling runAway() from
            // now on.
            brain.setState(runAway);
        }
    }

    /**
    * The "goHome" state.
    * It makes the ant move towards its home.
    */
    public function goHome() :void {
        // Move the ant towards home
        velocity = new Vector3D(Game.instance.home.x - position.x, Game.instance.home.y - position.y);

        if (distance(Game.instance.home, this) <= 10) {
            // The ant is home, let's find the leaf again.
            brain.setState(findLeaf);
        }
    }

    /**
    * The "runAway" state.
    * It makes the ant run away from the mouse cursor.
    */
    public function runAway() :void {
        // Move the ant away from the mouse cursor
        velocity = new Vector3D(position.x - Game.mouse.x, position.y - Game.mouse.y);

        // Is the mouse cursor still close?
        if (distance(Game.mouse, this) > MOUSE_THREAT_RADIUS) {
            // No, the mouse cursor has gone away. Let's go back looking for the leaf.
            brain.setState(findLeaf);
        }
    }

    public function update():void {
        // Update the FSM controlling the "brain". It will invoke the currently
        // active state function: findLeaf(), goHome() or runAway().
        brain.update();

        // Apply the velocity vector to the position, making the ant move.
        moveBasedOnVelocity();
    }

    (...)
}

1
이 링크가 질문에 대한 답변을 제공 할 수 있지만 여기에 답변의 필수 부분을 포함시키고 참조 용 링크를 제공하는 것이 좋습니다. 링크 된 페이지가 변경되면 링크 전용 답변이 유효하지 않을 수 있습니다. - 검토에서
drneel

@drneel 튜토리얼에서 비트를 복사하여 붙여 넣을 수는 있지만 저자로부터 신용을 빼앗아 가지 않습니까?
제트 블루

1
@JetBlue : 답변에 링크를 참조로 남겨두고 다른 사람의 저작권을 침해하지 않도록 답변 게시물에 자신의 단어에 관련 비트를 포함시킵니다. 나는 그것이 엄격 해 보인다는 것을 알고 있지만이 규칙으로 인해 많은 대답이 훨씬 좋아졌습니다.
Flimm

6

오늘 저는 주 디자인 패턴에 대해 깊이 알고 있습니다. C #의 스레딩 그림에 설명 된 것처럼 C #의 스레딩과 동일한 (+/-) ThreadState를 수행하고 테스트 했습니다.

여기에 이미지 설명을 입력하십시오

새 상태를 쉽게 추가하고 한 상태에서 다른 상태로 이동을 구성 할 수 있으므로 상태 구현에 캡슐화되어 있으므로 매우 쉽습니다.

구현 및 사용 : 상태 디자인 패턴에 따라 .NET ThreadState 구현


1
연결이 끊어졌습니다. 다른 거 있어요?

5

나는 C #으로 FSM을 구현하려고 시도하지 않았지만, 이러한 모든 소리 (또는 모양)는 과거에 C 또는 ASM과 같은 저수준 언어에서 FSM을 처리하는 방식에 매우 복잡합니다.

내가 항상 알고있는 방법을 "반복 루프"라고합니다. 여기에는 본질적으로 이벤트 (인터럽트)를 기반으로 주기적으로 종료 한 다음 다시 메인 루프로 돌아가는 'while'루프가 있습니다.

인터럽트 처리기 내에서 CurrentState를 전달하고 NextState를 반환하면 주 루프의 CurrentState 변수를 덮어 씁니다. 프로그램이 닫힐 때까지 (또는 마이크로 컨트롤러가 재설정 될 때까지)이 작업을 무한정 수행합니다.

다른 답변을보고있는 것은 FSM이 구현하려는 방식과 비교할 때 매우 복잡해 보입니다. 그것의 아름다움은 단순함에 있으며, FSM은 많은, 많은 상태와 전환으로 인해 매우 복잡 할 수 있지만 복잡한 프로세스는 쉽게 분해되고 소화 될 수 있습니다.

응답에 다른 질문이 포함되어서는 안된다는 것을 알고 있지만, 다른 제안 된 솔루션이 왜 그렇게 복잡해 보입니까?
그들은 거대한 썰매 망치로 작은 못을 치는 것과 비슷해 보입니다.


1
완전히 동의하십시오. switch 문을 사용하는 간단한 while 루프는 최대한 간단합니다.
굴림

2
여러 상태와 조건을 가진 매우 복잡한 상태 시스템이 없다면 여러 개의 중첩 스위치가 생길 수 있습니다. 또한 루프 구현에 따라 통화 중 대기에 대한 패널티가있을 수 있습니다.
Sune Rievers

3

한판 승부 상태입니다. 그것이 당신의 필요에 맞습니까?

나는 그 맥락과 관련이 있다고 생각하지만 확실히 가치가 있습니다.

http://en.wikipedia.org/wiki/State_pattern

이를 통해 귀하의 주에서는 "개체"클래스가 아닌 어디로 가야할지 결정할 수 있습니다.

브루노


1
상태 패턴은 상태 / 모드에 따라 다르게 작동 할 수있는 클래스를 처리하며 상태 간 전환을 처리하지 않습니다.
Eli Algranti

3

제 생각에는 상태 머신은 상태를 변경하는 것뿐만 아니라 특정 상태 내에서 트리거 / 이벤트를 처리하기 위해 (매우 중요) 의미합니다. 상태 머신 디자인 패턴을 더 잘 이해하려면 320 페이지 헤드 퍼스트 디자인 패턴 책에서 좋은 설명을 찾을 수 있습니다 .

변수 내의 상태뿐만 아니라 다른 상태 내의 트리거 처리에 관한 것입니다. 이해하기 쉬운 설명이 들어있는 훌륭한 장 (그리고 아니오, 이것을 언급하는 데 비용이 들지 않습니다 :-).


3

방금 이것에 기여했습니다.

https://code.google.com/p/ysharp/source/browse/#svn%2Ftrunk%2FStateMachinesPoC

다음은 상태가 IObserver (신호) 인 명령의 직접 및 간접 전송 명령을 시연하여 신호 소스 IObservable (신호)에 대한 응답자를 보여주는 예제 중 하나입니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Test
{
    using Machines;

    public static class WatchingTvSampleAdvanced
    {
        // Enum type for the transition triggers (instead of System.String) :
        public enum TvOperation { Plug, SwitchOn, SwitchOff, Unplug, Dispose }

        // The state machine class type is also used as the type for its possible states constants :
        public class Television : NamedState<Television, TvOperation, DateTime>
        {
            // Declare all the possible states constants :
            public static readonly Television Unplugged = new Television("(Unplugged TV)");
            public static readonly Television Off = new Television("(TV Off)");
            public static readonly Television On = new Television("(TV On)");
            public static readonly Television Disposed = new Television("(Disposed TV)");

            // For convenience, enter the default start state when the parameterless constructor executes :
            public Television() : this(Television.Unplugged) { }

            // To create a state machine instance, with a given start state :
            private Television(Television value) : this(null, value) { }

            // To create a possible state constant :
            private Television(string moniker) : this(moniker, null) { }

            private Television(string moniker, Television value)
            {
                if (moniker == null)
                {
                    // Build the state graph programmatically
                    // (instead of declaratively via custom attributes) :
                    Handler<Television, TvOperation, DateTime> stateChangeHandler = StateChange;
                    Build
                    (
                        new[]
                        {
                            new { From = Television.Unplugged, When = TvOperation.Plug, Goto = Television.Off, With = stateChangeHandler },
                            new { From = Television.Unplugged, When = TvOperation.Dispose, Goto = Television.Disposed, With = stateChangeHandler },
                            new { From = Television.Off, When = TvOperation.SwitchOn, Goto = Television.On, With = stateChangeHandler },
                            new { From = Television.Off, When = TvOperation.Unplug, Goto = Television.Unplugged, With = stateChangeHandler },
                            new { From = Television.Off, When = TvOperation.Dispose, Goto = Television.Disposed, With = stateChangeHandler },
                            new { From = Television.On, When = TvOperation.SwitchOff, Goto = Television.Off, With = stateChangeHandler },
                            new { From = Television.On, When = TvOperation.Unplug, Goto = Television.Unplugged, With = stateChangeHandler },
                            new { From = Television.On, When = TvOperation.Dispose, Goto = Television.Disposed, With = stateChangeHandler }
                        },
                        false
                    );
                }
                else
                    // Name the state constant :
                    Moniker = moniker;
                Start(value ?? this);
            }

            // Because the states' value domain is a reference type, disallow the null value for any start state value : 
            protected override void OnStart(Television value)
            {
                if (value == null)
                    throw new ArgumentNullException("value", "cannot be null");
            }

            // When reaching a final state, unsubscribe from all the signal source(s), if any :
            protected override void OnComplete(bool stateComplete)
            {
                // Holds during all transitions into a final state
                // (i.e., stateComplete implies IsFinal) :
                System.Diagnostics.Debug.Assert(!stateComplete || IsFinal);

                if (stateComplete)
                    UnsubscribeFromAll();
            }

            // Executed before and after every state transition :
            private void StateChange(IState<Television> state, ExecutionStep step, Television value, TvOperation info, DateTime args)
            {
                // Holds during all possible transitions defined in the state graph
                // (i.e., (step equals ExecutionStep.LeaveState) implies (not state.IsFinal))
                System.Diagnostics.Debug.Assert((step != ExecutionStep.LeaveState) || !state.IsFinal);

                // Holds in instance (i.e., non-static) transition handlers like this one :
                System.Diagnostics.Debug.Assert(this == state);

                switch (step)
                {
                    case ExecutionStep.LeaveState:
                        var timeStamp = ((args != default(DateTime)) ? String.Format("\t\t(@ {0})", args) : String.Empty);
                        Console.WriteLine();
                        // 'value' is the state value that we are transitioning TO :
                        Console.WriteLine("\tLeave :\t{0} -- {1} -> {2}{3}", this, info, value, timeStamp);
                        break;
                    case ExecutionStep.EnterState:
                        // 'value' is the state value that we have transitioned FROM :
                        Console.WriteLine("\tEnter :\t{0} -- {1} -> {2}", value, info, this);
                        break;
                    default:
                        break;
                }
            }

            public override string ToString() { return (IsConstant ? Moniker : Value.ToString()); }
        }

        public static void Run()
        {
            Console.Clear();

            // Create a signal source instance (here, a.k.a. "remote control") that implements
            // IObservable<TvOperation> and IObservable<KeyValuePair<TvOperation, DateTime>> :
            var remote = new SignalSource<TvOperation, DateTime>();

            // Create a television state machine instance (automatically set in a default start state),
            // and make it subscribe to a compatible signal source, such as the remote control, precisely :
            var tv = new Television().Using(remote);
            bool done;

            // Always holds, assuming the call to Using(...) didn't throw an exception (in case of subscription failure) :
            System.Diagnostics.Debug.Assert(tv != null, "There's a bug somewhere: this message should never be displayed!");

            // As commonly done, we can trigger a transition directly on the state machine :
            tv.MoveNext(TvOperation.Plug, DateTime.Now);

            // Alternatively, we can also trigger transitions by emitting from the signal source / remote control
            // that the state machine subscribed to / is an observer of :
            remote.Emit(TvOperation.SwitchOn, DateTime.Now);
            remote.Emit(TvOperation.SwitchOff);
            remote.Emit(TvOperation.SwitchOn);
            remote.Emit(TvOperation.SwitchOff, DateTime.Now);

            done =
                (
                    tv.
                        MoveNext(TvOperation.Unplug).
                        MoveNext(TvOperation.Dispose) // MoveNext(...) returns null iff tv.IsFinal == true
                    == null
                );

            remote.Emit(TvOperation.Unplug); // Ignored by the state machine thanks to the OnComplete(...) override above

            Console.WriteLine();
            Console.WriteLine("Is the TV's state '{0}' a final state? {1}", tv.Value, done);

            Console.WriteLine();
            Console.WriteLine("Press any key...");
            Console.ReadKey();
        }
    }
}

참고 :이 예제는 다소 인공적이며 대부분 여러 직교 피쳐를 시연하기위한 것입니다. CRTP를 사용하여 완전한 클래스로 상태 값 도메인 자체를 구현할 필요는 거의 없습니다 ( http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern 참조). .

다음은 동일한 상태 머신과 동일한 테스트 케이스에 대해 훨씬 간단하고 아마도 훨씬 일반적인 구현 사용 사례 (상태 값 도메인으로 간단한 열거 형 사용)입니다.

https://code.google.com/p/ysharp/source/browse/trunk/StateMachinesPoC/WatchingTVSample.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Test
{
    using Machines;

    public static class WatchingTvSample
    {
        public enum Status { Unplugged, Off, On, Disposed }

        public class DeviceTransitionAttribute : TransitionAttribute
        {
            public Status From { get; set; }
            public string When { get; set; }
            public Status Goto { get; set; }
            public object With { get; set; }
        }

        // State<Status> is a shortcut for / derived from State<Status, string>,
        // which in turn is a shortcut for / derived from State<Status, string, object> :
        public class Device : State<Status>
        {
            // Executed before and after every state transition :
            protected override void OnChange(ExecutionStep step, Status value, string info, object args)
            {
                if (step == ExecutionStep.EnterState)
                {
                    // 'value' is the state value that we have transitioned FROM :
                    Console.WriteLine("\t{0} -- {1} -> {2}", value, info, this);
                }
            }

            public override string ToString() { return Value.ToString(); }
        }

        // Since 'Device' has no state graph of its own, define one for derived 'Television' :
        [DeviceTransition(From = Status.Unplugged, When = "Plug", Goto = Status.Off)]
        [DeviceTransition(From = Status.Unplugged, When = "Dispose", Goto = Status.Disposed)]
        [DeviceTransition(From = Status.Off, When = "Switch On", Goto = Status.On)]
        [DeviceTransition(From = Status.Off, When = "Unplug", Goto = Status.Unplugged)]
        [DeviceTransition(From = Status.Off, When = "Dispose", Goto = Status.Disposed)]
        [DeviceTransition(From = Status.On, When = "Switch Off", Goto = Status.Off)]
        [DeviceTransition(From = Status.On, When = "Unplug", Goto = Status.Unplugged)]
        [DeviceTransition(From = Status.On, When = "Dispose", Goto = Status.Disposed)]
        public class Television : Device { }

        public static void Run()
        {
            Console.Clear();

            // Create a television state machine instance, and return it, set in some start state :
            var tv = new Television().Start(Status.Unplugged);
            bool done;

            // Holds iff the chosen start state isn't a final state :
            System.Diagnostics.Debug.Assert(tv != null, "The chosen start state is a final state!");

            // Trigger some state transitions with no arguments
            // ('args' is ignored by this state machine's OnChange(...), anyway) :
            done =
                (
                    tv.
                        MoveNext("Plug").
                        MoveNext("Switch On").
                        MoveNext("Switch Off").
                        MoveNext("Switch On").
                        MoveNext("Switch Off").
                        MoveNext("Unplug").
                        MoveNext("Dispose") // MoveNext(...) returns null iff tv.IsFinal == true
                    == null
                );

            Console.WriteLine();
            Console.WriteLine("Is the TV's state '{0}' a final state? {1}", tv.Value, done);

            Console.WriteLine();
            Console.WriteLine("Press any key...");
            Console.ReadKey();
        }
    }
}

'HTH


각 상태 인스턴스에 자체 상태 그래프 사본이있는 것이 조금 이상하지 않습니까?
Groo

@ 그루 : 아니, 그들은하지 않습니다. 모니 커에 대해 널 (null) 문자열이있는 개인 생성자를 사용하여 생성 된 Television의 인스턴스 만 상태 기계로서 상태 그래프를 갖습니다. 텔레비전의 이름을 가진 다른 사람들 ( 전통적이고 임시적인 목적을위한 모니 커가 없는 )은 단지 "고정 점 (state point)"상태로, 상태 상수 (상태 그래프 (들) 실제 상태 머신은 정점으로 참조됩니다). 'HTH,
YSharp

알았어. 어쨌든 IMHO, 실제로 이러한 전환을 처리하는 코드를 포함하면 더 좋을 것입니다. 이 방법은 라이브러리에 (IMHO) 명확하지 않은 인터페이스를 사용하는 예일뿐입니다. 예를 들어, 어떻게 StateChange해결됩니까? 반사를 통해? 정말 필요한가요?
Groo

1
@ 그루 : 좋은 말. 실제로 첫 번째 예제에서 핸들러를 반영 할 필요는 없습니다 (프로그래밍 방식으로 정확하게 수행되며 사용자 정의 속성과 달리). 따라서이 작업도 예상대로 작동합니다. private Television(string moniker, Television value) { Handler<Television, TvOperation, DateTime> myHandler = StateChange; // (code omitted) new { From = Television.Unplugged, When = TvOperation.Plug, Goto = Television.Off, With = myHandler } }
YSharp

1
노력해 주셔서 감사합니다!
Groo

3

일반 상태 머신을 만들었습니다Juliet의 코드 . 그것은 나를 위해 굉장히 노력하고 있습니다.

다음과 같은 이점이 있습니다.

  • 두 열거와 코드의 새로운 상태 머신을 만들 수 있습니다 TStateTCommand ,
  • TransitionResult<TState>출력 결과를 더 잘 제어 할 수있는 구조체 추가[Try]GetNext()메소드
  • 중첩 클래스를 노출 StateTransition 만을 통해 AddTransition(TState, TCommand, TState)함께 작업을 더 쉽게하기

암호:

public class StateMachine<TState, TCommand>
    where TState : struct, IConvertible, IComparable
    where TCommand : struct, IConvertible, IComparable
{
    protected class StateTransition<TS, TC>
        where TS : struct, IConvertible, IComparable
        where TC : struct, IConvertible, IComparable
    {
        readonly TS CurrentState;
        readonly TC Command;

        public StateTransition(TS currentState, TC command)
        {
            if (!typeof(TS).IsEnum || !typeof(TC).IsEnum)
            {
                throw new ArgumentException("TS,TC must be an enumerated type");
            }

            CurrentState = currentState;
            Command = command;
        }

        public override int GetHashCode()
        {
            return 17 + 31 * CurrentState.GetHashCode() + 31 * Command.GetHashCode();
        }

        public override bool Equals(object obj)
        {
            StateTransition<TS, TC> other = obj as StateTransition<TS, TC>;
            return other != null
                && this.CurrentState.CompareTo(other.CurrentState) == 0
                && this.Command.CompareTo(other.Command) == 0;
        }
    }

    private Dictionary<StateTransition<TState, TCommand>, TState> transitions;
    public TState CurrentState { get; private set; }

    protected StateMachine(TState initialState)
    {
        if (!typeof(TState).IsEnum || !typeof(TCommand).IsEnum)
        {
            throw new ArgumentException("TState,TCommand must be an enumerated type");
        }

        CurrentState = initialState;
        transitions = new Dictionary<StateTransition<TState, TCommand>, TState>();
    }

    /// <summary>
    /// Defines a new transition inside this state machine
    /// </summary>
    /// <param name="start">source state</param>
    /// <param name="command">transition condition</param>
    /// <param name="end">destination state</param>
    protected void AddTransition(TState start, TCommand command, TState end)
    {
        transitions.Add(new StateTransition<TState, TCommand>(start, command), end);
    }

    public TransitionResult<TState> TryGetNext(TCommand command)
    {
        StateTransition<TState, TCommand> transition = new StateTransition<TState, TCommand>(CurrentState, command);
        TState nextState;
        if (transitions.TryGetValue(transition, out nextState))
            return new TransitionResult<TState>(nextState, true);
        else
            return new TransitionResult<TState>(CurrentState, false);
    }

    public TransitionResult<TState> MoveNext(TCommand command)
    {
        var result = TryGetNext(command);
        if(result.IsValid)
        {
            //changes state
            CurrentState = result.NewState;
        }
        return result;
    }
}

이것은 TryGetNext 메소드의 리턴 유형입니다.

public struct TransitionResult<TState>
{
    public TransitionResult(TState newState, bool isValid)
    {
        NewState = newState;
        IsValid = isValid;
    }
    public TState NewState;
    public bool IsValid;
}

사용하는 방법:

이것이 당신이 만드는 방법입니다 OnlineDiscountStateMachine 일반 클래스에서를 .

OnlineDiscountState상태 및 열거 형에 대한 열거 형 정의OnlineDiscountCommand 명령에 .

클래스 정의 OnlineDiscountStateMachine이 두 열거 형을 사용하여 일반 클래스에서 파생 된

초기 상태 가로 설정 base(OnlineDiscountState.InitialState)되도록 생성자를 파생시킵니다.OnlineDiscountState.InitialState

AddTransition필요한만큼 여러 번 사용

public class OnlineDiscountStateMachine : StateMachine<OnlineDiscountState, OnlineDiscountCommand>
{
    public OnlineDiscountStateMachine() : base(OnlineDiscountState.Disconnected)
    {
        AddTransition(OnlineDiscountState.Disconnected, OnlineDiscountCommand.Connect, OnlineDiscountState.Connected);
        AddTransition(OnlineDiscountState.Disconnected, OnlineDiscountCommand.Connect, OnlineDiscountState.Error_AuthenticationError);
        AddTransition(OnlineDiscountState.Connected, OnlineDiscountCommand.Submit, OnlineDiscountState.WaitingForResponse);
        AddTransition(OnlineDiscountState.WaitingForResponse, OnlineDiscountCommand.DataReceived, OnlineDiscountState.Disconnected);
    }
}

파생 된 상태 머신 사용

    odsm = new OnlineDiscountStateMachine();
    public void Connect()
    {
        var result = odsm.TryGetNext(OnlineDiscountCommand.Connect);

        //is result valid?
        if (!result.IsValid)
            //if this happens you need to add transitions to the state machine
            //in this case result.NewState is the same as before
            Console.WriteLine("cannot navigate from this state using OnlineDiscountCommand.Connect");

        //the transition was successfull
        //show messages for new states
        else if(result.NewState == OnlineDiscountState.Error_AuthenticationError)
            Console.WriteLine("invalid user/pass");
        else if(result.NewState == OnlineDiscountState.Connected)
            Console.WriteLine("Connected");
        else
            Console.WriteLine("not implemented transition result for " + result.NewState);
    }

1

Juliet가 제안한 상태 머신은 실수가 있다고 생각합니다. GetHashCode 메소드 는 두 가지 다른 전이에 대해 동일한 해시 코드를 리턴 할 수 있습니다. 예를 들면 다음과 같습니다.

상태 = 활성 (1), 명령 = 일시 중지 (2) => HashCode = 17 + 31 + 62 = 110

State = Paused (2), Command = End (1) => HashCode = 17 + 62 + 31 = 110

이 오류를 피하려면 방법은 다음과 같아야합니다.

public override int GetHashCode()
   {
            return 17 + 23 * CurrentState.GetHashCode() + 31 * Command.GetHashCode();
   }

알렉스


1
해시 코드는 가능한 모든 조합에 대해 고유 한 숫자를 반환 할 필요가 없으며 대상 범위 전체에 분포가 잘 구분 된 고유 한 값만 반환합니다 (이 경우 범위는 모든 가능한 값임 int). 이것이 HashCode항상와 함께 구현되는 이유 입니다 Equals. 해시 코드가 동일하면 Equals메소드를 사용하여 객체의 정확한 eqaulity를 확인합니다 .
Dmitry Avtonomov 5

0

FiniteStateMachine은 C # 링크로 작성된 단순 상태 머신입니다.

내 라이브러리 FiniteStateMachine을 사용할 때의 이점 :

  1. "컨텍스트"클래스를 정의하여 외부 세계에 단일 인터페이스를 제공하십시오.
  2. 상태 추상 기본 클래스를 정의하십시오.
  3. 상태 머신의 다른 "상태"를 State 기본 클래스의 파생 클래스로 나타냅니다.
  4. 적절한 State 파생 클래스에서 상태 별 동작을 정의하십시오.
  5. "컨텍스트"클래스에서 현재 "상태"에 대한 포인터를 유지하십시오.
  6. 상태 머신의 상태를 변경하려면 현재 "상태"포인터를 변경하십시오.

DLL 다운로드 다운로드

LINQPad의 예 :

void Main()
{
            var machine = new SFM.Machine(new StatePaused());
            var output = machine.Command("Input_Start", Command.Start);
            Console.WriteLine(Command.Start.ToString() + "->  State: " + machine.Current);
            Console.WriteLine(output);

            output = machine.Command("Input_Pause", Command.Pause);
            Console.WriteLine(Command.Pause.ToString() + "->  State: " + machine.Current);
            Console.WriteLine(output);
            Console.WriteLine("-------------------------------------------------");
}
    public enum Command
    {
        Start,
        Pause,
    }

    public class StateActive : SFM.State
    {

        public override void Handle(SFM.IContext context)

        {
            //Gestione parametri
            var input = (String)context.Input;
            context.Output = input;

            //Gestione Navigazione
            if ((Command)context.Command == Command.Pause) context.Next = new StatePaused();
            if ((Command)context.Command == Command.Start) context.Next = this;

        }
    }


public class StatePaused : SFM.State
{

     public override void Handle(SFM.IContext context)

     {

         //Gestione parametri
         var input = (String)context.Input;
         context.Output = input;

         //Gestione Navigazione
         if ((Command)context.Command == Command.Start) context.Next = new  StateActive();
         if ((Command)context.Command == Command.Pause) context.Next = this;


     }

 }

1
GNU GPL 라이센스가 있습니다.
Der_Meister

0

state.cs를 추천 합니다 . 나는 개인적으로 state.js (JavaScript 버전)를 사용했으며 매우 기쁘게 생각합니다. 그 C # 버전도 비슷한 방식으로 작동합니다.

상태를 인스턴스화합니다.

        // create the state machine
        var player = new StateMachine<State>( "player" );

        // create some states
        var initial = player.CreatePseudoState( "initial", PseudoStateKind.Initial );
        var operational = player.CreateCompositeState( "operational" );
        ...

일부 전환을 인스턴스화합니다.

        var t0 = player.CreateTransition( initial, operational );
        player.CreateTransition( history, stopped );
        player.CreateTransition<String>( stopped, running, ( state, command ) => command.Equals( "play" ) );
        player.CreateTransition<String>( active, stopped, ( state, command ) => command.Equals( "stop" ) );

상태 및 전환에 대한 동작을 정의합니다.

    t0.Effect += DisengageHead;
    t0.Effect += StopMotor;

그리고 그것은 (거의) 그것입니다. 자세한 내용은 웹 사이트를 참조하십시오.


0

NuGet에는 2 개의 인기있는 상태 머신 패키지가 있습니다.

Appccelerate.StateMachine (13.6K 다운로드 + 3.82K의 레거시 버전 (bbv.Common.StateMachine))

StateMachineToolkit (1.56K 다운로드)

Appccelerate lib에는 좋은 문서 가 있지만 .NET 4를 지원하지 않으므로 프로젝트에 StateMachineToolkit을 선택했습니다.


0

이 저장소의 다른 대안 https://github.com/lingkodsoft/StateBliss 는 유창한 구문을 사용하고 트리거를 지원합니다.

    public class BasicTests
    {
        [Fact]
        public void Tests()
        {
            // Arrange
            StateMachineManager.Register(new [] { typeof(BasicTests).Assembly }); //Register at bootstrap of your application, i.e. Startup
            var currentState = AuthenticationState.Unauthenticated;
            var nextState = AuthenticationState.Authenticated;
            var data = new Dictionary<string, object>();

            // Act
            var changeInfo = StateMachineManager.Trigger(currentState, nextState, data);

            // Assert
            Assert.True(changeInfo.StateChangedSucceeded);
            Assert.Equal("ChangingHandler1", changeInfo.Data["key1"]);
            Assert.Equal("ChangingHandler2", changeInfo.Data["key2"]);
        }

        //this class gets regitered automatically by calling StateMachineManager.Register
        public class AuthenticationStateDefinition : StateDefinition<AuthenticationState>
        {
            public override void Define(IStateFromBuilder<AuthenticationState> builder)
            {
                builder.From(AuthenticationState.Unauthenticated).To(AuthenticationState.Authenticated)
                    .Changing(this, a => a.ChangingHandler1)
                    .Changed(this, a => a.ChangedHandler1);

                builder.OnEntering(AuthenticationState.Authenticated, this, a => a.OnEnteringHandler1);
                builder.OnEntered(AuthenticationState.Authenticated, this, a => a.OnEnteredHandler1);

                builder.OnExiting(AuthenticationState.Unauthenticated, this, a => a.OnExitingHandler1);
                builder.OnExited(AuthenticationState.Authenticated, this, a => a.OnExitedHandler1);

                builder.OnEditing(AuthenticationState.Authenticated, this, a => a.OnEditingHandler1);
                builder.OnEdited(AuthenticationState.Authenticated, this, a => a.OnEditedHandler1);

                builder.ThrowExceptionWhenDiscontinued = true;
            }

            private void ChangingHandler1(StateChangeGuardInfo<AuthenticationState> changeinfo)
            {
                var data = changeinfo.DataAs<Dictionary<string, object>>();
                data["key1"] = "ChangingHandler1";
            }

            private void OnEnteringHandler1(StateChangeGuardInfo<AuthenticationState> changeinfo)
            {
                // changeinfo.Continue = false; //this will prevent changing the state
            }

            private void OnEditedHandler1(StateChangeInfo<AuthenticationState> changeinfo)
            {                
            }

            private void OnExitedHandler1(StateChangeInfo<AuthenticationState> changeinfo)
            {                
            }

            private void OnEnteredHandler1(StateChangeInfo<AuthenticationState> changeinfo)
            {                
            }

            private void OnEditingHandler1(StateChangeGuardInfo<AuthenticationState> changeinfo)
            {
            }

            private void OnExitingHandler1(StateChangeGuardInfo<AuthenticationState> changeinfo)
            {
            }

            private void ChangedHandler1(StateChangeInfo<AuthenticationState> changeinfo)
            {
            }
        }

        public class AnotherAuthenticationStateDefinition : StateDefinition<AuthenticationState>
        {
            public override void Define(IStateFromBuilder<AuthenticationState> builder)
            {
                builder.From(AuthenticationState.Unauthenticated).To(AuthenticationState.Authenticated)
                    .Changing(this, a => a.ChangingHandler2);

            }

            private void ChangingHandler2(StateChangeGuardInfo<AuthenticationState> changeinfo)
            {
                var data = changeinfo.DataAs<Dictionary<string, object>>();
                data["key2"] = "ChangingHandler2";
            }
        }
    }

    public enum AuthenticationState
    {
        Unauthenticated,
        Authenticated
    }
}

0

내 솔루션을 사용할 수 있습니다. 이것이 가장 편리한 방법입니다. 또한 무료입니다.

단계로 상태 머신을 작성 하십시오.

1. 노드 에디터 🔗 에서 스키마를 생성 하고 library를 사용하여 프로젝트에로드

StateMachine stateMachine = 새로운 StateMachine ( "scheme.xml");

2. 이벤트에 대한 앱 로직 설명

stateMachine.GetState ( "State1"). OnExit (Action1);
stateMachine.GetState ( "State2"). OnEntry (Action2);
stateMachine.GetTransition ( "Transition1"). OnInvoke (Action3);
stateMachine.OnChangeState (Action4);

3. 상태 머신을 실행합니다 🚘

stateMachine.Start ();

연결:

노드 편집기 : https://github.com/SimpleStateMachine/SimpleStateMachineNodeEditor

라이브러리 : https://github.com/SimpleStateMachine/SimpleStateMachineLibrary

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.