Git Product home page Git Product logo

stateless's Introduction

Stateless Build status NuGet Pre Release Join the chat at https://gitter.im/dotnet-state-machine/stateless Stack Overflow

Create state machines and lightweight state machine-based workflows directly in .NET code:

var phoneCall = new StateMachine<State, Trigger>(State.OffHook);

phoneCall.Configure(State.OffHook)
    .Permit(Trigger.CallDialled, State.Ringing);

phoneCall.Configure(State.Connected)
    .OnEntry(t => StartCallTimer())
    .OnExit(t => StopCallTimer())
    .InternalTransition(Trigger.MuteMicrophone, t => OnMute())
    .InternalTransition(Trigger.UnmuteMicrophone, t => OnUnmute())
    .InternalTransition<int>(_setVolumeTrigger, (volume, t) => OnSetVolume(volume))
    .Permit(Trigger.LeftMessage, State.OffHook)
    .Permit(Trigger.PlacedOnHold, State.OnHold);

// ...

phoneCall.Fire(Trigger.CallDialled);
Assert.AreEqual(State.Ringing, phoneCall.State);

This project, as well as the example above, was inspired by Simple State Machine (Archived).

Features

Most standard state machine constructs are supported:

  • Generic support for states and triggers of any .NET type (numbers, strings, enums, etc.)
  • Hierarchical states
  • Entry/exit actions for states
  • Guard clauses to support conditional transitions
  • Introspection

Some useful extensions are also provided:

  • Ability to store state externally (for example, in a property tracked by an ORM)
  • Parameterised triggers
  • Reentrant states
  • Export to DOT graph

Hierarchical States

In the example below, the OnHold state is a substate of the Connected state. This means that an OnHold call is still connected.

phoneCall.Configure(State.OnHold)
    .SubstateOf(State.Connected)
    .Permit(Trigger.TakenOffHold, State.Connected)
    .Permit(Trigger.PhoneHurledAgainstWall, State.PhoneDestroyed);

In addition to the StateMachine.State property, which will report the precise current state, an IsInState(State) method is provided. IsInState(State) will take substates into account, so that if the example above was in the OnHold state, IsInState(State.Connected) would also evaluate to true.

Entry/Exit actions

In the example, the StartCallTimer() method will be executed when a call is connected. The StopCallTimer() will be executed when call completes (by either hanging up or hurling the phone against the wall.)

The call can move between the Connected and OnHold states without the StartCallTimer() and StopCallTimer() methods being called repeatedly because the OnHold state is a substate of the Connected state.

Entry/Exit action handlers can be supplied with a parameter of type Transition that describes the trigger, source and destination states.

Internal transitions

Sometimes a trigger needs to be handled, but the state shouldn't change. This is an internal transition. Use InternalTransition for this.

Initial state transitions

A substate can be marked as initial state. When the state machine enters the super state it will also automatically enter the substate. This can be configured like this:

    sm.Configure(State.B)
        .InitialTransition(State.C);

    sm.Configure(State.C)
        .SubstateOf(State.B);

Due to Stateless' internal structure, it does not know when it is "started". This makes it impossible to handle an initial transition in the traditional way. It is possible to work around this limitation by adding a dummy initial state, and then use Activate() to "start" the state machine.

    sm.Configure(InitialState)
        .OnActivate(() => sm.Fire(LetsGo))
        .Permit(LetsGo, StateA)

External State Storage

Stateless is designed to be embedded in various application models. For example, some ORMs place requirements upon where mapped data may be stored, and UI frameworks often require state to be stored in special "bindable" properties. To this end, the StateMachine constructor can accept function arguments that will be used to read and write the state values:

var stateMachine = new StateMachine<State, Trigger>(
    () => myState.Value,
    s => myState.Value = s);

In this example the state machine will use the myState object for state storage.

Another example can be found in the JsonExample solution, located in the example folder.

Activation / Deactivation

It might be necessary to perform some code before storing the object state, and likewise when restoring the object state. Use Deactivate and Activate for this. Activation should only be called once before normal operation starts, and once before state storage.

Introspection

The state machine can provide a list of the triggers that can be successfully fired within the current state via the StateMachine.PermittedTriggers property. Use StateMachine.GetInfo() to retrieve information about the state configuration.

Guard Clauses

The state machine will choose between multiple transitions based on guard clauses, e.g.:

phoneCall.Configure(State.OffHook)
    .PermitIf(Trigger.CallDialled, State.Ringing, () => IsValidNumber)
    .PermitIf(Trigger.CallDialled, State.Beeping, () => !IsValidNumber);

Guard clauses within a state must be mutually exclusive (multiple guard clauses cannot be valid at the same time.) Substates can override transitions by respecifying them, however substates cannot disallow transitions that are allowed by the superstate.

The guard clauses will be evaluated whenever a trigger is fired. Guards should therefore be made side effect free.

Parameterised Triggers

Strongly-typed parameters can be assigned to triggers:

var assignTrigger = stateMachine.SetTriggerParameters<string>(Trigger.Assign);

stateMachine.Configure(State.Assigned)
    .OnEntryFrom(assignTrigger, email => OnAssigned(email));

stateMachine.Fire(assignTrigger, "[email protected]");

Trigger parameters can be used to dynamically select the destination state using the PermitDynamic() configuration method.

Ignored Transitions and Reentrant States

Firing a trigger that does not have an allowed transition associated with it will cause an exception to be thrown.

To ignore triggers within certain states, use the Ignore(TTrigger) directive:

phoneCall.Configure(State.Connected)
    .Ignore(Trigger.CallDialled);

Alternatively, a state can be marked reentrant so its entry and exit actions will fire even when transitioning from/to itself:

stateMachine.Configure(State.Assigned)
    .PermitReentry(Trigger.Assigned)
    .OnEntry(() => SendEmailToAssignee());

By default, triggers must be ignored explicitly. To override Stateless's default behaviour of throwing an exception when an unhandled trigger is fired, configure the state machine using the OnUnhandledTrigger method:

stateMachine.OnUnhandledTrigger((state, trigger) => { });

State change notifications (events)

Stateless supports 2 types of state machine events:

  • State transition
  • State machine transition completed

State transition

stateMachine.OnTransitioned((transition) => { });

This event will be invoked every time the state machine changes state.

State machine transition completed

stateMachine.OnTransitionCompleted((transition) => { });

This event will be invoked at the very end of the trigger handling, after the last entry action has been executed.

Export to DOT graph

It can be useful to visualize state machines on runtime. With this approach the code is the authoritative source and state diagrams are by-products which are always up to date.

phoneCall.Configure(State.OffHook)
    .PermitIf(Trigger.CallDialled, State.Ringing, IsValidNumber);
    
string graph = UmlDotGraph.Format(phoneCall.GetInfo());

The UmlDotGraph.Format() method returns a string representation of the state machine in the DOT graph language, e.g.:

digraph {
  OffHook -> Ringing [label="CallDialled [IsValidNumber]"];
}

This can then be rendered by tools that support the DOT graph language, such as the dot command line tool from graphviz.org or viz.js. See http://www.webgraphviz.com for instant gratification. Command line example: dot -T pdf -o phoneCall.pdf phoneCall.dot to generate a PDF file.

Async triggers

On platforms that provide Task<T>, the StateMachine supports async entry/exit actions and so on:

stateMachine.Configure(State.Assigned)
    .OnEntryAsync(async () => await SendEmailToAssignee());

Asynchronous handlers must be registered using the *Async() methods in these cases.

To fire a trigger that invokes asynchronous actions, the FireAsync() method must be used:

await stateMachine.FireAsync(Trigger.Assigned);

Note: while StateMachine may be used asynchronously, it remains single-threaded and may not be used concurrently by multiple threads.

Advanced Features

Retaining the SynchronizationContext

In specific situations where all handler methods must be invoked with the consumer's SynchronizationContext, set the RetainSynchronizationContext property on creation:

var stateMachine = new StateMachine<State, Trigger>(initialState)
{
    RetainSynchronizationContext = true
};

Setting this is vital within a Microsoft Orleans Grain for example, which requires the SynchronizationContext in order to make calls to other Grains.

Building

Stateless runs on .NET runtime version 4+ and practically all modern .NET platforms by targeting .NET Framework 4.6.2, .NET Standard 2.0 and .NET 8.0. Visual Studio 2017 or later is required to build the solution.

Contributing

We welcome contributions to this project. Check CONTRIBUTING.md for more info.

Project Goals

This page is an almost-complete description of Stateless, and its explicit aim is to remain minimal.

Please use the issue tracker or the Discussions page if you'd like to report problems or discuss features.

(Why the name? Stateless implements the set of rules regarding state transitions, but, at least when the delegate version of the constructor is used, doesn't maintain any internal state itself.)

stateless's People

Contributors

abalfour avatar angularsen avatar arturkarbone avatar celloza avatar cmendible avatar crozone avatar deepakparamkusam avatar drdavient avatar edgars-pivovarenoks avatar ffmathy avatar fjod avatar henius84 avatar henningnt avatar henningtorsteinsenbouvet avatar lakritzator avatar leeoades avatar lg2de avatar mclift avatar nblumhardt avatar p-m-j avatar pardahlman avatar pdinnissen avatar pentp avatar pete-craig avatar smnbackwards avatar surajgupta avatar tlk avatar trgrote avatar vip32 avatar yevhen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

stateless's Issues

Tagging in source tree to match NuGet package releases.

Could you please provide tagging or branching (sorry, I'm not an expert) on the source repository so that it's easier to find out what are the sources exactly matching the released NuGet packages version?

Thanks in advance.

Parameterized triggers and multiple entry methods

I have a question about parameterized triggers. I need to be able to pass objects into the triggers for a state but I need to pass different objects into the same state change. Is this possible?

var createStayTrigger = sm.SetTriggerParameters(Trigger.CreateStay);
var createStayAfterItemTrigger = sm.SetTriggerParameters(Trigger.CreateStay);

sm.Configure(State.ProcessingStay)
.Permit(Trigger.Finish, State.Complete)
.Ignore(Trigger.Start)
.Ignore(Trigger.CreateStay)
.OnEntryFrom(createStayTrigger, e =>
{
// do something here with LineItemAddedEvent
})
.OnEntryFrom(createStayAfterItemTrigger, e =>
{
// do something here with ItemCreatedEvent
});

Please update Stateless to support .NET 4

I have used the latest version of Stateless (2.5.11) in a recent project. It worked fine on my development machine.

I ran into issues when I deployed to a UAT test machine.

I get the following error message whenever I call the CanFire method on the StateMachine:

System.Web.HttpUnhandledException (0x80004005): Exception of type 'System.Web.HttpUnhandledException' was thrown. ---> System.IO.FileLoadException: Could not load file or assembly 'System.Core, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e, Retargetable=Yes' or one of its dependencies. The given assembly name or codebase was invalid. (Exception from HRESULT: 0x80131047) File name: 'System.Core, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e, Retargetable=Yes'
at Stateless.StateMachine2.StateRepresentation.TryFindLocalHandler(TTrigger trigger, TriggerBehaviour& handler) at Stateless.StateMachine2.StateRepresentation.TryFindHandler(TTrigger trigger, TriggerBehaviour& handler)
at Stateless.StateMachine`2.CanFire(TTrigger trigger)
at Company.MyProject.Entities.Models.Form.TryUpdateStatus(FormTriggers trigger) in ...

The only way I was able to get around this was to install the Nuget package "Stateless for .NET 4.0" (https://www.nuget.org/packages/stateless-4.0/).

It would be nice if the changes made in that clone could be incorporated into the official stateless project so that stateless works properly in .NET 4.

how can i fire with some args?

stateMachine.Configure("a").onEntry( id => OnEntryWithID(id) )

private void OnEntryWithID(int id)
{
Console.Write("id={0}",id);
}

stateMachine.Fire("a", 1111);

very thanks!!!!!!!!!!!!!!!!!!

PermitIfElse

Given PermitIf takes a Boolean guard clause, it would be useful to have a PermitIfElse function:

PermitIfElse(trigger, stateIfTrue, stateIfFalse, Func guard)

Allow IEquatable states and triggers

Hi.

If State and Trigger types aren't "primitive"/"scalar" types, but POCO classes, StateMachine model doesn't work at all!

Any suggestions?

Thanks.

C#6.0

Would an upgrade to new standards be a good idea at this point or wait for v2.

Yet another cannot install and use in VS 201x issue

In this case VS 2013

I really have trouble figuring out why a project as excellent, and interesting, as this one doesn't provide a clear statement about what is required to use it ... a simple statement. Do you really want new users to be frustrated figuring out why it won't compile ... why the 'nameof operator doesn't exist ?

I am able to use NuGet to install it in VS 2013, but then I have to use ReSharper's reflection tools to look at the source.

I'd appreciate one clean package that does not target SilverLight, or Win Mobile that I can add to Visual Studio.

thanks, Bill

Visualizable data representation of the State machine

This is not an issue per se, but i am looking for a way to turn my existing State machines into flow charts of some sort. People say that it is good for you,
To be able to achive this i reckon i would have to turn my state machine into some kind of data structure that is more easy to visualize, like a 2-dimensional array or something. I would then turn it into JSON and expose it to some SVG JavaScript library to make it all shiny.

I'm quite new to Stateless, but i wonder if anyone has done anything like this and if it is possible.

Transition from SubState to SuperState

I would have expected this behaviour, because it is something like a reentrance into the state.

[TestFixture]
public class StateRepresentationFixture
{ // extends StateRepresentationFixture
    [Test]
    public void WhenEnteringSuperstateFromSubstate_SuperstateEntryActionsExecuted()
    {  
        StateMachine<State, Trigger>.StateRepresentation super;
        StateMachine<State, Trigger>.StateRepresentation sub;
        CreateSuperSubstatePair(out super, out sub);
        bool executed = false;
        super.AddEntryAction((t,a) => executed = true );
        var transition = new StateMachine<State, Trigger>.Transition(sub.UnderlyingState, super.UnderlyingState, Trigger.X);
        super.Enter(transition);
        Assert.IsTrue(executed);
    }

    [Test]
    public void WhenExitingSubstateToSuperstate_SuperstateExitActionsExecuted()
    {
        StateMachine<State, Trigger>.StateRepresentation super;
        StateMachine<State, Trigger>.StateRepresentation sub;
        CreateSuperSubstatePair(out super, out sub);
        bool executed = false;
        super.AddExitAction(t => executed = true );
        var transition = new StateMachine<State, Trigger>.Transition(sub.UnderlyingState, super.UnderlyingState, Trigger.X);
        sub.Exit(transition);
        Assert.IsTrue(executed);
    }
}

Here are the changes for this

public void Enter(Transition transition, params object[] entryArgs)
{
    Enforce.ArgumentNotNull(transition, "transtion");

    if (transition.IsReentry || Includes(transition.Source)) // here
    {
        ExecuteEntryActions(transition, entryArgs);
    }
    else if (!Includes(transition.Source))
    {
        if (_superstate != null)
            _superstate.Enter(transition, entryArgs);

        ExecuteEntryActions(transition, entryArgs);
    }
}

public void Exit(Transition transition)
{
    Enforce.ArgumentNotNull(transition, "transtion");

    if (transition.IsReentry ||ย Includes(transition.Destination)) // here
    {
        ExecuteExitActions(transition);
    }
    else if (!Includes(transition.Destination))
    {
        ExecuteExitActions(transition);
        if (_superstate != null)
            _superstate.Exit(transition);
    }
}

ExitFrom

I don't if anyone here needed this, but at my project, I need to fire some actions on exit from one state.

Because of this state has the possibility to have a reentry, I can't control this only with the OnExit.

Is this a thing that only I have or does anybody had too?

Use a default constructor

In my opinion the state machine should not receive the initial state in the constructor.

This makes it almost impossible (at least cumbersome and unclean) to use it with a dependency injection container. The state machine should be initialized with fsm.Initialize(initialState)

Example / Documentation Around Persistence

The existing documentation

var stateMachine = new StateMachine<State, Trigger>(
    () => myState.Value,
    s => myState.Value = s);

doesn't really illustrate how one might use an actual ORM (Like EF) to persist the StateMachine to storage for long-lived processes or ones that live in transient processes.

Does the ORM-mapped object extend StateMachine? Does it have a StateMachine? How do you really wire up the materialization & saving beyond the conceptual example?

Any chance we can get a better example of the intended usage?

Thanks,

Paul

Ignored triggers are included in permittedTriggers

Re-posting on GitHub since I noticed project has moved:

What steps will reproduce the problem?

  1. Create a Statemachine with substates
  2. In a substate Ignore a trigger allowed in it's Superstate
  3. Transition your state machine to above substate
  4. Examine yourStateMachine.ToString()

The ignored trigger will be listed among the Permitted triggers even though the trigger won't be allowed by yourStateMachine.CanFire(substate)

Cause seem to be the union with the superstates permittedTriggers:

        public IEnumerable<TTrigger> PermittedTriggers
        {
            get
            {
                var result = _triggerBehaviours
                    .Where(t => t.Value.Any(a => a.IsGuardConditionMet))
                    .Select(t => t.Key);

                if (Superstate != null)
                    result = result.Union(Superstate.PermittedTriggers);

                return result.ToArray();
            }
        }

Build warning MSB3277 due to reference to System.Core, Version=2.0.5.0

(this was originally mentioned in https://github.com/nblumhardt/stateless/issues/14#issuecomment-158979430, but I thought i'd raise a proper issue)

We are using Stateless, version 2.5.29.0 in one of our projects, which is compiled targeting .net 4.5.

During build we get a warning Found conflicts between different versions of the same dependent assembly that could not be resolved. These reference conflicts are listed in the build log when log verbosity is set to detailed..

The detailed log shows:

...
5>  There was a conflict between "System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" and "System.Core, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e, Retargetable=Yes".
5>      "System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" was chosen because it was primary and "System.Core, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e, Retargetable=Yes" was not.
5>      References which depend on "System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" [C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5\System.Core.dll].
5>          C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5\System.Core.dll
5>            Project file item includes which caused reference "C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5\System.Core.dll".
... (25 lines ommitted) ...
5>          D:\XXX\Payments\Dev\Source\packages\NServiceBus.NHibernate.4.5.5\lib\net40\NServiceBus.NHibernate.dll
5>            Project file item includes which caused reference "D:\XXX\Payments\Dev\Source\packages\NServiceBus.NHibernate.4.5.5\lib\net40\NServiceBus.NHibernate.dll".
5>              NServiceBus.NHibernate, Version=4.5.0.0, Culture=neutral, PublicKeyToken=9fc386479f8a226c, processorArchitecture=MSIL
... (94 lines ommitted) ...
5>      References which depend on "System.Core, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e, Retargetable=Yes" [].
5>          D:\XXX\Payments\Dev\Source\XXX\bin\Debug\Stateless.dll
5>            Project file item includes which caused reference "D:\XXX\Payments\Dev\Source\XXX.Payments.Domain\bin\Debug\Stateless.dll".
5>              D:\XXX\Payments\Dev\Source\XXX\bin\Debug\XXX.Payments.Domain.dll
5>C:\Program Files (x86)\MSBuild\12.0\bin\Microsoft.Common.CurrentVersion.targets(1697,5): warning MSB3277: Found conflicts between different versions of the same dependent assembly that could not be resolved.  These reference conflicts are listed in the build log when log verbosity is set to detailed.
...

I've attempted to use a bindingRedirect to fix this issue, but it had no effect.

I've also attempted using the Stateless-4.0 package, but that seems to have fallen behind, and does not have the OnTransitioned functionality from nblumhardt@f63abbe, so it appears quite old.

Now, I know very little about PCL (but a lot more now than I knew an hour ago), but it seems that the root cause is the change to use PCL (nblumhardt@828d097).

I wonder if its a good idea to create a nuget package that supports .net 3.5 (which was requested in https://github.com/nblumhardt/stateless/issues/14#issuecomment-137317963), .net 4.0, .net 4.5 as well as the PCL profile 136.

What are your thoughts?

If you're of the opinion that this is a good idea, I'll take a look at the implementation, most likely using build configurations, and see if I can put together a PR.

StateConfiguration does not expose configured State and the StateMachine itself

The current implementation of the StateConfiguration does not expose the StateMachine that is currently configured. Not exposing the StateMachine makes it difficult and is a limitation for enriching the fluent interface using Extension Methods without changing the Stateless library itself (you have to add the current StateMachine as parameter which makes some "noise" that shouldn't be required).

This is an example of an Extension Method if you have a Trigger that flows along several states and should be automatically triggered/fired when reaching a state:

public static class StateMachineExtensions
{
    public static StateMachine<TState, TTrigger>.StateConfiguration RepeatFireFor<TState, TTrigger>(
        this StateMachine<TState, TTrigger>.StateConfiguration configuration,
        TTrigger trigger,
        IEnumerable<TState> destinations,
        StateMachine<TState, TTrigger> stateMachine)
    {
        return configuration.OnEntry(
            transition =>
                {
                    if (!transition.IsReentry && destinations.Contains(transition.Destination))
                    {
                        stateMachine.Fire(trigger);
                    }
                });
    }

    public static StateMachine<TState, TTrigger>.StateConfiguration RepeatFireFor<TState, TTrigger>(
        this StateMachine<TState, TTrigger>.StateConfiguration configuration,
        TTrigger trigger,
        TState destination,
        StateMachine<TState, TTrigger> stateMachine)
    {
        return RepeatFireFor(configuration, trigger, stateMachine, destination);
    }

    public static StateMachine<TState, TTrigger>.StateConfiguration RepeatFireFor<TState, TTrigger>(
        this StateMachine<TState, TTrigger>.StateConfiguration configuration,
        TTrigger trigger,
        StateMachine<TState, TTrigger> stateMachine,
        params TState[] destinations)
    {
        return RepeatFireFor(configuration, trigger, destinations, stateMachine);
    }
}

This results in something like this:

stateMachine.Configure(State.SomeState)
    .Permit(Trigger.GotoNext, State.FirstNextState)
    .RepeatFireFor(Trigger.GotoNext, State.SecondNextState, stateMachine)
    .OnEntry(DoSomethingOnEnty);

Which could be much cleaner and less error prone (client is forced, could insert the wrong state machine):

stateMachine.Configure(State.SomeState)
    .Permit(Trigger.GotoNext, State.FirstNextState)
    .RepeatFireFor(Trigger.GotoNext, State.SecondNextState)
    .OnEntry(DoSomethingOnEnty);

... if you are able to write the Extensions this way:

public static class StateMachineExtensions
{
    public static StateMachine<TState, TTrigger>.StateConfiguration RepeatFireFor<TState, TTrigger>(
        this StateMachine<TState, TTrigger>.StateConfiguration configuration,
        TTrigger trigger,
        IEnumerable<TState> destinations)
    {
        return configuration.OnEntry(
            transition =>
                {
                    if (!transition.IsReentry && destinations.Contains(transition.Destination))
                    {
                                                // e.g. exposed StateMachine as property here
                        configuration.StateMachine.Fire(trigger); 
                    }
                });
    }

    public static StateMachine<TState, TTrigger>.StateConfiguration RepeatFireFor<TState, TTrigger>(
        this StateMachine<TState, TTrigger>.StateConfiguration configuration,
        TTrigger trigger,
        TState destination)
    {
        return RepeatFireFor(configuration, trigger, destination);
    }

    public static StateMachine<TState, TTrigger>.StateConfiguration RepeatFireFor<TState, TTrigger>(
        this StateMachine<TState, TTrigger>.StateConfiguration configuration,
        TTrigger trigger,
        params TState[] destinations)
    {
        return RepeatFireFor(configuration, trigger, destinations);
    }
}

The provision of the StateMachine by the StateConfiguration should be easy, because the StateMachine acts as Factory for the StateMachineConfiguration (even though the current StateConfiguration does not know about the TState/TTrigger generic parameters itself).

Another problem is that you do not expose the State that is Wrapped into the StateRepresentation's UnderlyingState that is currently not accessible outside of the StateConfiguration. This way you don't know what state (e.g. enum value) you are currently configuring when creating an Extension Method and you are forced to add some noise to the interface again (add the configured state as param, which the client/user already knows from the factory call .Configure(TState state).

Error handling during transition

How can errors be handled during state transition? Say, an error caused during OnEntry, or OnExit which shouldn't be causing the state change.

best practice for parameterized triggers in conjunction with a domain object?

Hello,

Maybe I'm just missing something simple/obvious in my wiring, but I'm struggling to figure out how this should be implemented.

The short version of my question: is there a way to call Fire() on parameterized triggers without having to store a reference to SetTriggerParameters and passing it around? Or am I missing something fundamental and going about it the wrong way?

The long version of my question...

the implementation in the readme does the following in the config:

var assignTrigger = stateMachine.SetTriggerParameters<string>(Trigger.Assign);

stateMachine.Configure(State.Assigned)
    .OnEntryFrom(assignTrigger, email => OnAssigned(email));

and then immediately calls .Fire(assignTrigger, "[email protected]"). Note the immediate use of assignTrigger. However, how is this expected to work when the call to .Fire() comes elsewhere, like in a domain object?

private void MyMethod_OnDomainObject(){
    _machine.Fire( ___what goes here?___, "[email protected]");
}

what should be the first argument? It doesn't seem right that I would have to store assignTrigger as a class property somewhere. This is what's done in the bug tracker example:

 public Bug(string title)
        {
        // ...
        _assignTrigger = _machine.SetTriggerParameters<string>(Trigger.Assign);
        // ....
}

but seems like it might quickly get out of hand when you have multiple parameterized triggers.

I took a look at the source and I see that there are methods such as this overload of Fire():

public void Fire<TArg0>(TriggerWithParameters<TArg0> trigger, TArg0 arg0) { // snipped }

I've tried multiple variations of calling this from my domain object to no avail, so maybe I'm just goofing up my generics / etc? Such as calling _machine.Fire<string>(); and trying to pass in an argument that matches the StateMachine<State,Event>.TriggerWithParameters<string> trigger, string arg0 signature. Can this be done? Or am I missing something fundamental and going about it the wrong way?

VS 2012

Solution is VS 2012 but the project uses C# 6.0 (nameof etc.) and requires VS2015. Please update solution file to 2015 format to clarify what is really needed to build or stay with older compatible syntax that enables build with 2012.

Personally, I use 2013 and 2015. With the free available Express Editions there seems to be not much benefit with having an old 2012 Solution file (that does not build in 2012 or 2013).

List Triggers and their Next States from current State

I want to generate list of Triggers and their Next States from current state.

Just like you exposed StateMachine.PermittedTriggers which returns list of allowed Triggers from current State, why don't you return IEnumerable NextStates.

Or there is a method that I can convert a Start State and Trigger to Next States cause I am still new :)

OnExit() called for all transitions upon Fire() call

Please, consider running the following code:

    class Program
    {
        public enum Page
        {
            ChooseLanguage,
            ProductList,
            Quantity
        }

        public enum Trigger
        {
            Back,
            Forward
        }

        static void Main(string[] args)
        {           

            var retail = new StateMachine<Page, Trigger>(Page.ChooseLanguage);

            retail.Configure(Page.ChooseLanguage)
                .Permit(Trigger.Forward, Page.ProductList)
                .OnExit(Log);

            retail.Configure(Page.ProductList)
                .Permit(Trigger.Back, Page.ChooseLanguage)
                .OnExit(Log);

            retail.Configure(Page.ProductList)
                .Permit(Trigger.Forward, Page.Quantity)
                .OnExit(Log);

            retail.Configure(Page.Quantity)
                .Permit(Trigger.Back, Page.ProductList)
                .OnExit(Log);

            while (retail.CanFire(Trigger.Forward))
            {
                retail.Fire(Trigger.Forward);
            } 
        }

        static void Log(StateMachine<Page, Trigger>.Transition transition)
        {
            Console.WriteLine("{0} -> {1}, Trigger: {2}", transition.Source, transition.Destination, transition.Trigger);
        }
    }

It'll give you the following output:

ChooseLanguage -> ProductList, Trigger: Forward
ProductList -> Quantity, Trigger: Forward
ProductList -> Quantity, Trigger: Forward

Thanks.

OnEntryFrom() and InternalTransition() overloads consistency

There is following OnEntryFrom() overload :

public StateConfiguration OnEntryFrom<TArg0>(TriggerWithParameters<TArg0> trigger, Action<TArg0> entryAction, string entryActionDescription = null);

For InternalTransition() the closest we currently have is :

public StateConfiguration InternalTransition<TArg0>(TriggerWithParameters<TArg0> trigger, Action<TArg0, Transition> entryAction)

Ddifference is in action argument :

  • Action<TArg0, Transition> entryAction
  • Action<TArg0> entryAction.

Would be nice to have same overload for InternalTransition() so I we can switch actions from OnEntryFrom() to InternalTransitions() without need to change the action signature by adding Transition argument.

PS. Perhaps we could have same set of overloads for both methods?

Force State

Hi,
is there a possibility to force the state machine to switch into certain state? E.g. if I have undo functionality in my application it could be necessary to set a certain state after an undo command.

Be able to provide external configuration

First of all forgive me, this isnt an issue but rather a general question. (and if its been asked, apologize about that as well)

I'd like to provide a bit of external config... eg, let users define states etc. Does stateless support definition and/or states from something other than an enum?

I dont mind writing something that initializes the container the proper way etc, but I cant really easily generate enums on the fly.

Firing triggers from OnEntry/OnExit actions.

This is something I do quite frequently - an OnEntry action either directly or indirectly results a call to Fire() that takes the state machine to a new state.

The problem is that because the call to Fire immediately causes a new transition, any code after the call to Fire in the OnEntry action is called after the state machine enters the new state. Here's an example:

class Program
{
    enum Trigger
    {
        T1, T2, T3
    }

    enum State
    {
        S1, S2, S3
    }

    static void Main(string[] args)
    {
        var check = true;

        var sm = new StateMachine<State, Trigger>(State.S1);

        sm.Configure(State.S1)
            .Permit(Trigger.T1, State.S2)
            .OnEntry(() => Console.WriteLine("State S1 OnEntry action"));

        sm.Configure(State.S2)
            .Permit(Trigger.T2, State.S3)
            .Permit(Trigger.T3, State.S1)
            .OnEntry(() =>
                {
                    Console.WriteLine("State S2 OnEntry action started");
                    if (check)
                        sm.Fire(Trigger.T2);
                    else
                        sm.Fire(Trigger.T3);
                    Console.WriteLine("State S2 OnEntry action complete");
                });

        sm.Configure(State.S3)
            .Permit(Trigger.T1, State.S1)
            .OnEntry(() => Console.WriteLine("State S3 OnEntry action"));

        sm.Fire(Trigger.T1);
    }
}

This results in the output:

State S2 OnEntry action started
State S3 OnEntry action
State S2 OnEntry action complete

I.e. S2 completes after S3.

I have a simple fix for this that add an internal queue to the state machine so that processing of triggers is carried in strict order.

Do you want the fix?

Support Async OnEntry, OnExit actions

Async tasks are currently fired, and forgotten. Things can get mangled up easily. An AsyncState machine requires three behaviors:

  1. Queued actions
  2. Fire and Forget
  3. Fire only after completion, or drop.

Thoughts?

Target .NET Core

Now that .NET Core is out Stateless makes sense as a project that targets .NET core

Can we have InternalTransition, please?

I have used the Quantum Leaps state machine framework quite a bit to run state machines on microcontrollers. It supports internal transitions, and it makes life a lot easier. The internal transition does not actually change state, so the state is the same after the event has been processed.

Consider the telephone call example. Let's say we need to adjust the volume. Every state would need an two extra lines: PermitReentry (AdjustVolumeEvent) and OnEntryFrom(AdjustVolumeEvent, HandleVolumeControl).

By using internal transition we only need to add a handler to a superstate (State.OffHook), which is much less verbose.
As a bonus the super states becomes much more useful as well.

Querying StateMachine for current State

Hello all, long time user of Stateless and first time poster!

Disclaimer: I am not very experienced with C# and taught myself programing through a lot of trial and error, apologies in advance for disturbing you f^^;)...

My states are classes deriving from a simple BaseState I made:
https://gist.github.com/arun02139/d34e70e3b4ac9441a8af

Sometimes I want to check the state of a machine but I have to do so by checking the state type against the type of the current State using a custom function I added . Is there a more elegant way to do this when I don't want the state classes themselves to be exposed outside of the 'Battle' class that maintains the state-machine?

Thanks for your time and thoughts, everyone!

Hierarchical state representation in dotgraph?

Hello, sorry if this is the wrong place to post f^^;) I just pulled the latest and printed out a few of my SMs and hierarchical information doesn't seem to be represented (no sub-states) โ€“ just wanted to check if that's not currently supported or else I'm doing something wrong f^^;)

Thank you!

Arun

Access Modifiers are very restrictive

I'm subclassing StateMachine and was hoping I would be able to find a nice way to set up OnUnhandledTrigger so that if a state transition exists for given state and transition but guard conditions are not met that the exception message could contain the text from PermitIf->guardDescription.

Sadly everything I could possibly use is either internal or private.

Would there be any interest in making more use of protected access modifier?

Concurrent States

We would like to extend Stateless to support multiple concurrent states. For example, AcceptPayment should transition to both SendConfirmationEmail and OrderFromFactory. We plan to start out by make TState into List and go from there. Has anybody tried this yet? Thx! allan

Transition / Actions

Hi All,

First many thanks for this amazing library. I have been using it in many projects for the past 2 years, it's been very helpful.

I have however a small concern about a feature which seems to be missing, with regards to UML 2 state machines specifications.

No action can be attached to an event. There is no possibility to execute an action on a Fire itself.
The only way to achieve that today would be to create a Parameterised Triggers and then to execute the attached function on an EntryFrom.
But this does not cover all the cases.

Let's take the example below :
dessin1

In this example, the machine is deterministic, but depending on when and from where the event T1 is raise the associated action is not the same.

I though it would be amazing to configure a State mahcine like this:

stateMachine.Configure(State.A)
    .PermitIf(Trigger.T1, State.B, () => { return cond(); }, (a,b) => { Action1(a,b); } )
    .PermitIf(Trigger.T1, State.B, () => { return !cond(); }, (c) => { Action3(c); } );

stateMachine.Configure(State.B)
    .PermitReentry(Trigger.T1, (a,b) => { Action1(a,b)} );

Any idea on how to do that with Stateless ?
Anyone tried to extend the library this way ?

Thanks in advance.

Please update Nuget version

I downloaded Stateless from Nuget just yesterday, and I need the PermitDynamic function with more arguments.
In my project, reflection doesn't work for more than one argument. Anyway, in the test units I tried to make a new one with two arguments, and it works! That's because the test units come from GIT, and that issue has been fixed just two weeks ago.
Looking for some information about Nuget versioning, I found out that in the AssemblyInfo file (in the Properties subfolder) there is an older version (2.4) than the one on the Nuget repository (2.5.29), so I think I got an old version of the library in my project.
Thank you.

"Live" state-machine information with ToDotGraph()

Is it possible to use ToDotGraph() to generate dynamic information about a running state machine ? For instance, would it be possible to visualize and actualize dynamically the actual and the previous state ?

MissingManifestResourceException in build 2.5.79

I have a unit test that expects InvalidOperationException to throw when attempting to fire in a state where that trigger is not allowed. Was working fine until I updated from 2.5.62. The exception is throwing on the call to ResourceManager.GetString(name, cultureInfo).

Assert.Throws() Failure
Expected: typeof(System.InvalidOperationException)
Actual: typeof(System.Resources.MissingManifestResourceException): Unable to load resources for resource file "Stateless.StateMachineResources" in package "8c4135be-e11e-423c-829b-76933c7da27f".

Runtime visualization through a customizable ToString method

Hello!
I have found it useful to be able to visualize the state configuration of state machines on runtime. With this approach the code is the authoritative source and state diagrams a by-product and therefore always up to date.

I have made a proof of concept which you can see at nblumhardt/stateless@master...tlk:stringformatter

The idea is to make it possible for library users to implement the IStringFormatter interface and pass a custom formatter object to StateMachine.ToString which will then return a custom string representation of the state machine.

In the test code there is a DotGraphFormatter implementation which generates output in the DOT graph language.

Example output from DotGraphFormatter:

digraph {
 A -> B [label="X"];
 A -> C [label="Y [IsTrue]"];
}

This can then be processed by https://github.com/ellson/graphviz/ or other tools that support the DOT graph language, e.g. http://www.webgraphviz.com

Ideas or suggestions on better approaches are most welcome!
Would you be interested in making the Stateless library support something like this?

Best regards,
Thomas

how can i know the source state?

for example:

i have 3 states: A B C and A,B can go state C...so when i in OnEnterC() how can i know the source is A or B?

very thanks....

Trigger history for compound transitions

I recently started using Stateless and have found it to be extremely useful! Thank you!

My question is about compound state transitions. I am using Stateless to model workflow, and late in the workflow I am required to know whether the machine traversed one path or another. Is there a way look back to where the state machine has been? I would prefer to not have to externally record my own "Fire" invocations, because that would force me to keep additional state variables, which Stateless has so far been great at helping me avoid. What is the "correct" way of keeping track of multiple transitions?

Unity Visual Inspector

Ok so, this is totally a feature request and not an "issue" -- Nick just tell me if this post is in the wrong place and I'll take it down / move it :)

So, I was wondering if there is anyone who wants to try and build a visual inspector for Unity that works with Stateless together with me. This work probably doesn't belong as part of the 'Stateless' repo itself, but could be another Github project (and possibly released in the Unity Asset Store too).

Phase 1 would be a pure visualization of state hierarchy and flow during runtime.

Phase 2 would be to allow the machine to be setup through the inspector itself (would require converting a Unity editor-made diagram / meta-data into auto-generated C# code...? lol not sure)

Just a thought, would like to hear feedback on this idea from anyone who's used Stateless and Unity together ^^

How to split a larger statemachine in separate sub-statemachines as distinct classes?

Substates are great, but when state machine starts to have a number of states that have substates, the whole thing starts to be a little bit too large, and less easy to manage. As per question, is there a way to split a larger state-machine in a number of smaller sub state-machines?

Just by mean of example, think as if I have a state machine made of LoggedOutState and LoggedInState, which in turn has a number of substates, UserEditing, UserAdding, ...
I would like to define a high-level state-machine with the two states, and then a state-machine dedicated to just the LoggedIn scenarios. Then somehow make it so that triggers defined for entering in LoggedInState could start/enter a state in the lower-level state-machine, as well as triggers defined to exit LoggedInState could stop/leave a lower-level state.

Thanks all

PS we just posted also a question on StackOverflow.

Question, how to ignore triggers with certain parameter values?

I have a parametrized trigger, and in most cases, i would like to ignore it, unless it happens with some specific parameter values. How to properly achieve this? The guard expressions in 'If' methods do not receive trigger parameters.
The only way it seems, is to do PermitDynamic, and return the current state if you want to ignore the trigger?
Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.