C # basit devlet makine örneği?


259

Güncelleme:

Yine örnekler için teşekkürler, çok yardımcı oldular ve aşağıdaki ile onlardan bir şey almak istemiyorum.

Şu anda verilen örnekler, onları ve devlet makinelerini anladığım kadarıyla, bir devlet makinesi tarafından genellikle anladığımızın sadece yarısı değil mi?
Örneklerin durumu değiştirmesi açısından, ancak bu yalnızca bir değişkenin değerini değiştirerek (ve farklı durumlarda farklı değer değişikliklerine izin vererek) temsil edilirken, genellikle bir durum makinesi de davranışını ve davranışını (yalnızca) değiştirmemelidir. duruma bağlı olarak bir değişken için farklı değer değişikliklerine izin verme duygusu, ancak farklı durumlar için farklı yöntemlerin yürütülmesine izin verme anlamındadır.

Yoksa durum makineleri ve bunların ortak kullanımları hakkında yanlış bir fikrim var mı?

Saygılarımla


Orijinal soru:

Ben devlet makineleri ve yineleme blokları hakkında c # ve devlet makineleri oluşturmak için araçlar ve C # için ne bu tartışma buldum , bu yüzden soyut şeyler bir sürü buldum ama bir noob olarak tüm bu biraz kafa karıştırıcı.

Birisi belki sadece 3,4 devlet ile basit bir devlet makinesi gerçekleştiren bir C # kaynak kodu-örnek sağlayabilir, harika olurdu, sadece onu elde etmek için harika olurdu.



Genel olarak devlet makineleri mi yoksa sadece yineleyici tabanlı makineler mi merak ediyorsunuz?
Skurmedel

2
İncelemeye değer: hanselman.com/blog/…
zmische

Yanıtlar:


417

Bu basit durum diyagramıyla başlayalım:

basit durum makine diyagramı

Sahibiz:

  • 4 durum (Etkin değil, Etkin, Duraklatıldı ve Çıkıldı)
  • 5 durum geçişi türü (Begin Command, End Command, Pause Command, Resume Command, Exit Command).

Bunu, geçerli durum ve komut üzerinde bir anahtar deyimi gerçekleştirmek veya bir geçiş tablosundaki geçişleri aramak gibi birkaç yolla C # 'a dönüştürebilirsiniz. Bu basit durum makinesi için, aşağıdakileri kullanarak temsil etmesi çok kolay olan bir geçiş tablosunu tercih ederim 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();
        }
    }
}

Kişisel tercih olarak, durum makinelerimi bir GetNextsonraki durumu belirleyici olarak döndürecek bir MoveNextişlev ve durum makinesini değiştirecek bir işlevle tasarlamayı seviyorum .


66
GetHashCode()Asal kullanımının doğru uygulanması için +1 .
ja72

13
Bana GetHashCode () 'un amacını açıklar mısınız?
Siddharth

14
@Siddharth: StateTransitionSınıf sözlükte anahtar olarak kullanılır ve anahtarların eşitliği önemlidir. StateTransitionAynı geçişi temsil ettikleri sürece (örneğin CurrentStateve aynı oldukları) iki farklı örneği eşit kabul edilmelidir Command. Eşitliğini uygulamak için geçersiz kılmak zorunda Equalssıra sıra GetHashCode. Özellikle sözlük karma kodunu kullanır ve iki eşit nesne aynı karma kodunu döndürmelidir. Çok fazla eşit olmayan nesne aynı karma kodunu paylaşmıyorsa iyi performans elde edersiniz, bu nedenle GetHashCodegösterildiği gibi uygulanır.
Martin Liversage

14
Bu kesinlikle bir devlet makinesi (ve aynı zamanda uygun bir C # 'ish uygulaması) alırken, hala OP'nin davranış değiştirme sorusunun cevabını eksik olduğunu hissediyorum? Sonuçta, sadece durumları hesaplar, ancak durum değişiklikleri, programın gerçek et ve genellikle Giriş / Çıkış olayları olarak adlandırılan davranış hala eksiktir.
stijn

2
Birisi buna ihtiyaç duyarsa: Bu tate makinesini ayarladım ve birlik oyunumda kullandım. Git hub'da kullanılabilir: github.com/MarcoMig/Finite-State-Machine-FSM
Max_Power89

73

Mevcut açık kaynaklı Sonlu Durum Makinelerinden birini kullanmak isteyebilirsiniz. Örneğin, bbv.Common.StateMachine http://code.google.com/p/bbvcommon/wiki/StateMachine adresinde bulundu . Çok sezgisel akıcı sözdizimi ve giriş / çıkış eylemleri, geçiş eylemleri, korumalar, hiyerarşik, pasif uygulama (arayanın iş parçacığında yürütülür) ve aktif uygulama (fsm'nin çalıştığı kendi iş parçacığı, olaylar bir kuyruğa eklenir).

Juliets'e örnek olarak devlet makinesi tanımını yapmak çok kolaylaşır:

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);

Güncelleme : Proje konumu şu adrese taşındı: https://github.com/appccelerate/statemachine


4
Bu mükemmel açık kaynaklı durum makinesine başvurduğunuz için teşekkür ederiz. Mevcut durumu nasıl alabilirim?
Ramazan Polat

3
Yapamazsınız ve yapmamalısınız. Devlet kararsız bir şeydir. Durumu talep ettiğinizde bir geçişin ortasında olmanız mümkündür. Tüm eylemler geçişler, durum girişi ve durum çıkışları içinde yapılmalıdır. Gerçekten duruma sahip olmak istiyorsanız, yerel bir alan ekleyebilir ve durumu bir giriş eylemine atayabilirsiniz.
Remo Gloor

4
Soru, neye "ihtiyaç duyduğunuz" ve gerçekten SM durumuna veya başka bir devlet türüne ihtiyacınız varsa. Örneğin, bazı görüntüleme metnine ihtiyacınız varsa, belirtilen birkaç öğenin aynı görüntüleme metnine sahip olması, örneğin gönderime hazırlanma işleminin birden fazla alt durumu varsa. Bu durumda, tam olarak ne yapmak istediğinizi yapmalısınız. Bazı ekran metinlerini doğru yerlerde güncelleyin. Örneğin ExecuteOnEntry içinde. Daha fazla bilgiye ihtiyacınız varsa, yeni bir Soru sorun ve sorununuzu tam olarak belirtin, çünkü bu konudan çıkıyor.
Remo Gloor

Tamam, yeni bir soru soruyorum ve cevaplamanı bekliyorum. Çünkü en iyi cevaba sahip olduğunuzdan, ancak hala sorgulayıcı kabul etmediğinden başka birinin bu sorunu çözdüğünü düşünmüyorum. Burada soru url'si göndereceğim. Teşekkürler.
Ramazan Polat

4
Akıcı ve beyan edici API için +1. Bu harika. BTW, google kodu eski gibi görünüyor. En yeni proje siteleri burada
KFL

52

Çok basitleştirilmiş bir elektronik cihazı (TV gibi) modelleyen çok klasik bir sonlu durum makinesine bir örnek

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
devlet makinelerinde yeni olan herkes için, bu önce ayaklarını ıslatmak için mükemmel bir ilk örnektir.
PositiveGuy

2
Devlet makinelerine yeniyim ve cidden, bu bana Işık getirdi - teşekkürler!
MC5

1
Bu uygulamayı beğendim. Bu sorunla karşılaşan herkes için hafif bir "iyileştirme". FSM sınıfında private void DoNothing() {return;}tüm null örneklerini ekledim ve değiştirdim this.DoNothing. Mevcut durumu döndürmenin hoş bir yan etkisi vardır.
Sethmo011

1
Bu isimlerden bazılarının arkasında bir neden olup olmadığını merak ediyorum. Bunun baktığınızda, ilk sezgi öğelerini yeniden adlandırmak etmektir Statesiçin Unpowered, Standby, On. Benim akıl yürütmem, eğer birisi bana televizyonumun hangi durumda olduğunu sorarsa, "Başlat" değil "Kapalı" derdim. Ben de değişti StandbyWhenOnve StandbyWhenOffhiç TurnOnve TurnOff. Bu kodu daha sezgisel bir şekilde okur, ancak terminolojimi daha az uygun hale getiren konvansiyonlar veya diğer faktörler olup olmadığını merak ediyorum.
Jason Hamje

Makul görünüyor, herhangi bir eyalet adlandırma sözleşmesini gerçekten takip etmiyordum; her model için mantıklı.
Pete Stensønes

20

Bazı utanmaz öz tanıtım burada, ama bir süre önce YieldMachine adlı sınırlı karmaşıklık durum makinesinin çok temiz ve basit bir şekilde tanımlanmasını sağlayan bir kütüphane oluşturdum . Örneğin, bir lambayı düşünün:

bir lamba devlet makinesi

Bu durum makinesinin 2 tetikleyiciye ve 3 duruma sahip olduğuna dikkat edin. YieldMachine kodunda, devletle ilgili tüm davranışlar için tek bir yöntem yazıyoruz, burada her durum için korkunç bir vahşet kullanıyoruz goto. Bir tetikleyici Action, adı verilen bir özellikle süslenmiş bir özellik veya alan alanına dönüşür Trigger. İlk durumun kodunu ve geçişlerini aşağıda yorumladım; sonraki durumlar aynı modeli izler.

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();
    }
}

Kısa ve güzel, ha!

Bu durum makinesi, sadece tetikler göndererek kontrol edilir:

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

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

Sadece açıklığa kavuşturmak için, bunu nasıl kullanacağınızı anlamanıza yardımcı olmak için ilk duruma bazı yorumlar ekledim.

    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

        ...

Bu çalışır çünkü C # derleyici aslında kullanılan her yöntem için dahili bir durum makinesi oluşturdu yield return. Bu yapı genellikle tembel olarak veri dizileri oluşturmak için kullanılır, ancak bu durumda aslında döndürülen diziyle (zaten null olan her şeydir) değil, başlık altında oluşturulan durum davranışıyla ilgilenmiyoruz.

StateMachineTaban sınıfı her birine atama koduna inşaat bazı yansıması yapar [Trigger]setleri eylem, Triggerüye ve ileriye devlet makinesini hareket eder.

Ancak onu kullanabilmek için iç kısımları gerçekten anlamanız gerekmez.


2
"Git" yalnızca yöntemler arasında atlarsa iğrençtir. Neyse ki, C # 'da izin verilmiyor.
Brannon

İyi bir nokta! Aslında, statik olarak yazılmış herhangi bir dil bir gotoyöntem arasında izin vermeyi başarırsa çok etkilendim .
skrebbel

3
@Brannon: Hangi dil gotoyöntemler arasında atlamaya izin veriyor ? Bunun nasıl işe yarayacağını anlamıyorum. Hayır, gotosorunsaldır çünkü prosedürel programlama ile sonuçlanır (bu tek başına birim testi gibi güzel şeyleri karmaşıklaştırır), kod tekrarını teşvik eder ( InvalidTriggerher durum için nasıl eklenmesi gerektiğini fark eder ?) Ve son olarak program akışını takip etmeyi zorlaştırır. Bunu bu iş parçacığındaki diğer çözümlerle (çoğu) karşılaştırın ve bunun tüm FSM'nin tek bir yöntemde gerçekleştiği tek çözüm olduğunu göreceksiniz. Bu genellikle bir endişeyi dile getirmek için yeterlidir.
Groo

1
@Groo, GW-BASIC, örneğin. Yöntemleri ve hatta işlevleri olmamasına yardımcı olur. Bunun yanı sıra, bu örnekte neden "program akışını takip etmek daha zor" bulduğunuzu anlamakta çok zorlandım. Bu bir devlet makinesi, diğerinden bir devlete "gitmek" tek yaptığınız şey. Bu gotooldukça iyi bir harita .
skrebbel

3
GW-BASIC gotoişlevler arasında atlamaya izin verir , ancak işlevleri desteklemez mi? :) Haklısın, "takip etmesi daha zor" ifadesi daha genel bir gotokonudur, gerçekten de bu durumda çok fazla sorun yoktur.
Groo

13

Kod bloğunu düzenli bir şekilde yürütmenizi sağlayan bir yineleyici bloğunu kodlayabilirsiniz. Kod bloğunun nasıl parçalandığı gerçekten hiçbir şeye karşılık gelmek zorunda değildir, sadece nasıl kodlamak istediğinizdir. Örneğin:

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");
}

Bu durumda, CountToTen'i çağırdığınızda, henüz hiçbir şey yürütülmez. Elde ettiğiniz şey, durum makinesinin yeni bir örneğini oluşturabileceğiniz bir durum makine jeneratörüdür. Bunu GetEnumerator () çağırarak yaparsınız. Ortaya çıkan IEnumerator, MoveNext (...) öğesini çağırarak kullanabileceğiniz bir durum makinesidir.

Bu nedenle, bu örnekte, MoveNext (...) 'i ilk aradığınızda konsola "1" yazdığını görürsünüz ve MoveNext (...)' i bir sonraki aradığınızda 2, 3, 4 ve Gördüğünüz gibi, olayların nasıl gerçekleşmesi gerektiğini düzenlemek için kullanışlı bir mekanizmadır.


6
Zorunlu bağlantı adil uyarı
sehe

8

Burada farklı bir bakış açısıyla devlet makineleri olduğu için başka bir cevap daha gönderiyorum; çok görsel.

Orijinal cevabım klasik zorunluluk kodudur. Durum makine görselleştirmeyi basitleştiren dizi nedeniyle kod olarak oldukça görsel düşünüyorum. Olumsuz tüm bunları yazmak zorunda. Remos'un yanıtı, kazan plakası kodunu yazma çabalarını hafifletir, ancak çok daha az görseldir. Üçüncü alternatif var; gerçekten devlet makinesini çiziyor.

.NET kullanıyorsanız ve çalışma süresinin 4. sürümünü hedefleyebiliyorsanız, iş akışının durum makinesi etkinliklerini kullanma seçeneğiniz vardır . Bunlar özünde devlet makinesini çizmenize ( Juliet'in diyagramında olduğu gibi) ve WF çalışma zamanının sizin için çalıştırmasına izin verir.

Daha fazla ayrıntı için Windows Workflow Foundation ile Durum Makineleri Oluşturma MSDN makalesine ve en son sürüm için bu CodePlex sitesine bakın.

.NET'i hedeflerken her zaman tercih edeceğim seçenek budur, çünkü programcı olmayanları görmek, değiştirmek ve açıklamak kolaydır; Resimler dedikleri gibi bin kelimeye bedeldir!


Devlet makinesinin tüm iş akışı vakfının en iyi parçalarından biri olduğunu düşünüyorum!
fabsenet

7

Durum makinelerinin bir soyutlama olduğunu ve bir tane oluşturmak için belirli araçlara ihtiyacınız olmadığını hatırlamak yararlıdır, ancak araçlar yararlı olabilir.

Örneğin, işlevleri olan bir durum makinesi gerçekleştirebilirsiniz:

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);
}

Bu makine martıları avlayacak ve su balonlarıyla vurmaya çalışacaktı. Eğer kaçırırsa, vuruncaya kadar (gerçekçi beklentilerle yapabilir); Taciz edecek martılar çıkıncaya kadar avlanmaya devam ediyor.

Her işlev, her duruma karşılık gelir; başlangıç ​​ve bitiş (veya kabul ) durumları gösterilmez. Muhtemelen orada fonksiyonlar tarafından modellenenden daha fazla durum vardır. Örneğin, balonu ateşledikten sonra makine gerçekten öncekinden başka bir durumdadır, ancak bu ayrımın yapılmasının pratik olmadığına karar verdim.

Ortak bir yol, sınıfları devletleri temsil etmek için kullanmak ve sonra bunları farklı şekillerde bağlamaktır.


7

Bu harika öğretici çevrimiçi bulundu ve kafamı sonlu durum makineleri etrafında sarmama yardımcı oldu.

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

Öğretici dil agnostiktir, bu nedenle C # ihtiyaçlarınıza kolayca uyarlanabilir.

Ayrıca, kullanılan örneğin (yiyecek arayan bir karınca) anlaşılması kolaydır.


Eğiticiden:

resim açıklamasını buraya girin

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
Bu bağlantı soruyu cevaplayabilirken, cevabın önemli kısımlarını buraya eklemek ve bağlantıyı referans olarak sağlamak daha iyidir. Bağlantı verilen sayfa değişirse, yalnızca bağlantı yanıtları geçersiz olabilir. - Yorumdan
drneel

@drneel Ben öğretici bit kopyalayıp yapıştırabilirsiniz ... ama bu yazarın kredi almak olmaz mı?
Jet Blue

1
@JetBlue: Bağlantıyı referans olarak cevapta bırakın ve kimsenin telif hakkını ihlal etmemek için ilgili bitleri cevap kelimenize kendi kelimelerinize ekleyin. Sıkı göründüğünü biliyorum, ancak bu kural nedeniyle birçok cevap çok daha iyi hale geldi.
Flimm

6

Bugün Devlet Tasarım Deseninde derinlere iniyorum. C # iş parçacığında resimde açıklandığı gibi (#) C # iş parçacığına eşit (+/-) ThreadState yaptım ve test

resim açıklamasını buraya girin

Kolayca yeni durumlar ekleyebilir, bir eyaletten diğerine hareketleri yapılandırabilir, eyalet uygulamasında kapsadığı için çok kolaydır

Uygulama ve kullanma: Durum Tasarımına Göre .NET ThreadState'i uygular


2
Bağlantı öldü. Diğerine sahip misin?
rulolar

5

Henüz C # içinde bir FSM uygulamayı denemedim, ama bunların hepsi geçmişte FSM'leri C veya ASM gibi düşük seviyeli dillerde ele alma şeklimde çok karmaşık (veya bakıldığında).

Her zaman bildiğim yönteme "Yinelemeli Döngü" adı verildiğini düşünüyorum. İçinde, esasen olaylara (aralıklarla) göre periyodik olarak çıkan, ardından tekrar ana döngüye dönen bir 'while' döngüsüne sahipsiniz.

Kesme işleyicileri içinde, bir CurrentState iletir ve bir NextState döndürürsünüz; bu, daha sonra ana döngüdeki CurrentState değişkeninin üzerine yazar. Bu reklam sonsuz program kapanana kadar (veya mikro denetleyici sıfırlanana kadar) yaparsınız.

Diğer cevapları gördüğüm şey, bir FSM'nin, bence, nasıl uygulanacağına kıyasla çok karmaşık görünüyor; güzelliği basitliğinde yatar ve FSM birçok eyalet ve geçişle çok karmaşık olabilir, ancak karmaşık sürecin kolayca parçalanmasına ve sindirilmesine izin verir.

Yanıtımın başka bir soru içermemesi gerektiğinin farkındayım, ama sormaya zorlandım: önerilen diğer çözümler neden bu kadar karmaşık görünüyor?
Dev bir kızak çekiçiyle küçük bir çiviyi vurmaya benziyorlar.


1
Kesinlikle katılmak. Bir switch deyimli basit bir while döngüsü elde edebileceğiniz kadar basittir.
rulolar

2
Birden fazla iç içe anahtarla sonuçlanacağınız birçok durum ve koşula sahip çok karmaşık bir durum makineniz yoksa. Ayrıca, döngü uygulamanıza bağlı olarak, meşgul beklemede bir ceza olabilir.
Sune Rievers

3

Ne bir devlet StatePattern. Bu sizin ihtiyaçlarınıza uygun mu?

Ben onun bağlamı ile ilgili olduğunu düşünüyorum, ama kesinlikle bir çekim değer.

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

Bu, eyaletlerinizin "nesne" sınıfına değil, nereye gideceğine karar vermesine izin verir.

Bruno


1
Devlet modeli içinde bulunduğu duruma / moda göre farklı davranabilen bir sınıfla ilgilenir, devletler arasındaki geçişle ilgilenmez.
Eli Algranti

3

Bence bir durum makinesi sadece durumları değiştirmek için değil, aynı zamanda belirli bir durumdaki tetikleyicileri / olayları işlemek için de (çok önemli) içindir. Durum makinesi tasarım desenini daha iyi anlamak istiyorsanız, Head First Design Patterns (Sayfa İlk Tasarım Desenleri), sayfa 320 kitabında iyi bir açıklama bulabilirsiniz .

Bu sadece değişkenler içindeki durumlarla değil, aynı zamanda farklı durumlardaki tetikleyicileri ele almakla da ilgilidir. Büyük bölüm (ve hayır, sadece :-) kolay anlaşılır bir açıklama içeren bu bahseden benim için hiçbir ücret yoktur.


3

Ben sadece katkıda bulundum:

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

IObserver (sinyal) durumları ile komutların doğrudan ve dolaylı gönderilmesini gösteren örneklerden biri, bu nedenle IObserverable (sinyal) sinyal kaynağına yanıt verir:

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();
        }
    }
}

Not: Bu örnek oldukça yapaydır ve çoğunlukla bir dizi dikey özelliği tanıtmak içindir. CRTP'yi kullanarak durum değeri alanının kendisini tam bir sınıf tarafından uygulamaya nadiren ihtiyaç duyulmalıdır (bkz: http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern ).

Kesinlikle daha basit ve muhtemelen çok daha yaygın bir uygulama kullanım durumu (durum değeri etki alanı olarak basit bir numaralandırma türü kullanarak), aynı durum makinesi için ve aynı test senaryosu için:

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


Her durum örneğinin durum grafiğinin kendi kopyasına sahip olması biraz garip değil mi?
Groo

@ Groro: hayır, bilmiyorlar. Yalnızca takma ad için boş bir dizeyle özel kurucu kullanılarak oluşturulan Televizyon örnekleri (dolayısıyla, korunan 'Oluştur' yöntemini çağırır), durum makineleri olarak bir durum grafiğine sahip olacaktır. Televizyon örnekleri olarak adlandırılan diğerleri ( bu geleneksel ve ad-hoc amaç için boş olmayan bir takma adla ), devlet sabitleri (devlet grafiklerinin) gerçek durum makineleri köşeleri olarak referans alacaktır). 'HTH,
YSharp

Tamam anladım. Her neyse, IMHO, aslında bu geçişleri işleyen bir kod eklemiş olsaydınız daha iyi olurdu. Bu şekilde, yalnızca (IMHO) çok açık olmayan bir arabirimin kitaplığınız için kullanılmasına örnek olarak hizmet eder. Örneğin, nasıl StateChangeçözülür? Yansıma yoluyla mı? Bu gerçekten gerekli mi?
Groo

1
@ Groro: İyi not. Gerçekten de ilk örnekte işleyiciyi yansıtmak gerekli değildir, çünkü tam olarak programlı olarak orada yapılır ve statik olarak bağlanabilir / tip kontrol edilebilir (özel özellikler üzerinden olduğu gibi). Bu da beklendiği gibi çalışıyor: 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
Çaban için teşekkürler!
Groo

3

Bu genel durum makinesini Juliet'in kodundan çıkardım . Benim için harika çalışıyor.

Bunlar faydaları:

  • iki numara ile kod halinde yeni durum makinesi oluşturabilir TStateve TCommand,
  • yöntemlerin TransitionResult<TState>çıktı sonuçları üzerinde daha fazla kontrol sahibi olmak için yapı eklendi[Try]GetNext()
  • İç içe sınıf açığa StateTransition yalnızca aracılığıyla AddTransition(TState, TCommand, TState)onunla çalışmak için kolaylaştırıyoruz

Kod:

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;
    }
}

Bu, TryGetNext yönteminin dönüş türüdür:

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

Nasıl kullanılır:

OnlineDiscountStateMachineJenerik sınıftan a'yı şu şekilde oluşturabilirsiniz :

OnlineDiscountStateDurumları OnlineDiscountCommandiçin bir enum ve komutları için bir enum tanımlayın .

OnlineDiscountStateMachineBu iki numarayı kullanarak genel sınıftan türetilmiş bir sınıf tanımlayın

Dan kurucu türevi base(OnlineDiscountState.InitialState)bu yüzden ilk durumu ayarlanırOnlineDiscountState.InitialState

Gerektiği AddTransitionkadar kullanın

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);
    }
}

türetilmiş durum makinesini kullan

    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 tarafından önerilen durum makinesinin bir hatası olduğunu düşünüyorum: GetHashCode yöntemi , iki farklı geçiş için aynı karma kodu döndürebilir, örneğin:

Durum = Aktif (1), Komut = Duraklat (2) => HashCode = 17 + 31 + 62 = 110

Durum = Duraklatıldı (2), Komut = Son (1) => HashCode = 17 + 62 + 31 = 110

Bu hatayı önlemek için, yöntem şöyle olmalıdır:

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

Alex


1
Karma kod, olası bir kombinasyon için benzersiz bir sayı döndürmek için gerekli değildir, yalnızca hedef aralıkta iyi bir dağılıma sahip farklı bir değer (bu durumda aralık, olası tüm intdeğerlerdir). Bu yüzden HashCodeher zaman birlikte uygulanır Equals. Karma kodlar aynıysa, nesneler Equalsyöntem kullanılarak tam eşitlik açısından denetlenir .
Dmitry Avtonomov

0

FiniteStateMachine C # Link ile yazılmış Basit Durum Makinesi

FiniteStateMachine kütüphanemi kullanmanın avantajları:

  1. Dış dünyaya tek bir arayüz sunmak için bir "bağlam" sınıfı tanımlayın.
  2. Devlet soyut temel sınıfını tanımlar.
  3. Devlet temel sınıfının türetilmiş sınıfları olarak devlet makinesinin farklı "durumlarını" temsil eder.
  4. Duruma özel uygun sınıflarda duruma özgü davranışı tanımlayın.
  5. "Bağlam" sınıfındaki geçerli "duruma" bir işaretçi tutun.
  6. Durum makinesinin durumunu değiştirmek için mevcut "durum" işaretçisini değiştirin.

DLL İndir İndir

LINQPad örneği:

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 lisansına sahiptir.
Der_Meister

0

Ben öneriyoruz state.cs . Şahsen state.js (JavaScript sürümü) kullandım ve bundan çok memnunum. Bu C # sürümü benzer şekilde çalışır.

Durumları somutlaştırıyorsunuz:

        // 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" );
        ...

Bazı geçişleri somutlaştırıyorsunuz:

        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" ) );

Durumlar ve geçişler üzerindeki eylemleri tanımlarsınız:

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

Ve bu (hemen hemen). Daha fazla bilgi için web sitesine bakın.



0

Bu repodaki diğer alternatif https://github.com/lingkodsoft/StateBliss kullanılan akıcı sözdizimi, tetikleyicileri destekler.

    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

Çözümümü kullanabilirsiniz, bu en uygun yoldur. Ayrıca ücretsiz.

Üç adımda durum makinesi oluşturun :

1. Düğüm düzenleyicide şema oluşturun🔗 ve kullanarak projenize yükleyin kütüphaneyi projenize yükleyin📚

StateMachine stateMachine = yeni StateMachine ("scheme.xml");

2. Etkinliklerdeki uygulama mantığınızı açıklayın

stateMachine.GetState ( "State1") OnExit (action1.);
stateMachine.GetState ( "State2") OnEntry (action2.);
stateMachine.GetTransition ( "değişimi1") OnInvoke (Action3.);
stateMachine.OnChangeState (Action4);

3. Durum makinesini çalıştırın🚘

stateMachine.Start ();

Bağlantılar:

Düğüm editörü: https://github.com/SimpleStateMachine/SimpleStateMachineNodeEditor

Kütüphane: https://github.com/SimpleStateMachine/SimpleStateMachineLibrary

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.