TIM 2.0.0

dotnet add package TIM --version 2.0.0
NuGet\Install-Package TIM -Version 2.0.0
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="TIM" Version="2.0.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add TIM --version 2.0.0
#r "nuget: TIM, 2.0.0"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install TIM as a Cake Addin
#addin nuget:?package=TIM&version=2.0.0

// Install TIM as a Cake Tool
#tool nuget:?package=TIM&version=2.0.0

<a name="readme-top"></a>

Forks Stargazers Issues MIT License

<br /> <div align="center">

<h3 align="center">TIM</h3>

<p align="center"> The Interface Machine <br /> <a href="https://github.com/JulesVerhoeven/TIM"><strong>Explore the docs »</strong></a> <br /> <br />
<a href="https://github.com/JulesVerhoeven/TIM/issues">Report Bug</a> · <a href="https://github.com/JulesVerhoeven/TIM/issues">Request Feature</a> </p> </div>

<details> <summary>Table of Contents</summary> <ol> <li> <a href="#about-the-project">About The Project</a> <ul> <li><a href="#built-with">Built With</a></li> </ul> </li> <li> <a href="#getting-started">Getting Started</a> <ul> <li><a href="#prerequisites">Prerequisites</a></li> <li><a href="#installation">Installation</a></li> </ul> </li> <li><a href="#usage">Usage</a></li>

<li><a href="#contributing">Contributing</a></li>
<li><a href="#license">License</a></li>
<li><a href="#contact">Contact</a></li>
<li><a href="#acknowledgments">Acknowledgments</a></li>

</ol> </details>

About The Project

TIM is a C# library for state machine implementations that are triggered through a (proxy-)interface. This hides all state machine mechanics for the outside world, you just talk with an ordinary interface. This approach also opens the way to use ordinary classes (with interfaces) as states within the state machine. The library is thread-safe and it further supports all the normal state machine stuff like entry/exit etc.

<p align="right">(<a href="#readme-top">back to top</a>)</p>

Built With

Next

<p align="right">(<a href="#readme-top">back to top</a>)</p>

Getting Started

Prerequisites

The project only depends on .net core 6.0.

Installation

  • NuGet
    NuGet\Install-Package TIM -Version 2.0.0
    

<p align="right">(<a href="#readme-top">back to top</a>)</p>

Usage

A statemachine consists of four parts; a trigger interface, a context, the machine and the states. The trigger interface is a normal interface that must be implemented on every state. The context is used to store the data needed in the states. The machine represents the overall functionality of the state machine. The states do the actual implementation of the state machine. Lets have a look at a simple example: a lamp.

Example

A lamp has two states On and Off. So we will create an enum that represents these states:

public enum LampStates
{
   Off,
   On
}

Next, we define the trigger interface. A lamp can be turned on and off and, to make this example a bit more interesting, it can also blink. Here is the interface:

public interface ILampControl
{
    int BlinkDelayInMS { get; set; }
    void Blink();
    void TurnOff();
    void TurnOn();
}

Now we can also define the context. The states need to remember if they are in blinking mode and what is the blink delay. So here is the context:

public class LampContext
{
    public int BlinkDelayInMS { get; set; } = 500;
    public bool IsBlinking { get; set; }
}

In general, you will create a base state class that handles the properties of the interface. From the base state you will then derive the individual states. Every state must be derived from 'State<TKey, TContext>' and it must implement the trigger interface. In our case TKey is 'LampStates' and TContext is 'LampContext'. So here is the base state:

public abstract class LampBaseState : State<LampStates, LampContext>, ILampControl
{
    public int BlinkDelayInMS 
    {   
        get => Context.BlinkDelayInMS;
        set
        {
            Context.BlinkDelayInMS = value;
        }
    }
    public abstract void Blink();
    public abstract void TurnOff();
    public abstract void TurnOn();
}

Notice that the blink delay is not stored within the state but in the context. In general you should never store any data within a state. With the base state we can now define the states themselves.

public class LampStateOff : LampBaseState
{
    public override LampStates Key => LampStates.Off;

    protected override void OnEntry(LampStates from)
    {
        if (Context.IsBlinking)
        {
            CallTimer("Timer Blink Off", Context.BlinkDelayInMS, () => GoTo(LampStates.On));
        }
    }
    public override void Blink()
    {
        Context.IsBlinking = true;
        GoTo(LampStates.On);
    }
    public override void TurnOff()
    {
        Context.IsBlinking = false;
        GoTo(LampStates.Off);
    }
    public override void TurnOn()
    {
        Context.IsBlinking = false;
        GoTo(LampStates.On);
    }
}

and

public class LampStateOn : LampBaseState
{
    public override LampStates Key => LampStates.On;

    protected override void OnEntry(LampStates from)
    {
        if (Context.IsBlinking) 
        {
            CallTimer("Timer Blink On", Context.BlinkDelayInMS, () => GoTo(LampStates.Off));
        }
    }
    public override void Blink()
    {
        Context.IsBlinking= true;
        GoTo(LampStates.Off);
    }
    public override void TurnOff()
    {
        Context.IsBlinking = false;
        GoTo(LampStates.Off);
    }
    public override void TurnOn()
    {
        Context.IsBlinking = false;
        GoTo(LampStates.On);
    }   
}

There are several things to notice here:

  • The Key property: every state has a unique key that is used in the GoTo(...) method.
  • The OnEntry() override: The state machine always calls OnEntry before entering a new state. This is the place where you can perform initialization of the state. In this case we use it to start a timer for the blinking.
  • The GoTo(..) method is used to jump to a state. If GoTo has been called, the state machine will exit the current state and enter the new state after the trigger has finished. Upon the exit of a state, any running calls are cancelled. This happens also if the destination is the same as the current state. In this sample this is used to cancel the blink timer started in the entry.
  • The CallTimer(...) method: You should not await any task inside a trigger, instead use the Call() methods to do any asyn operation. Because the timer is a very common operation, it has a special call which is used here.

Now we have defined all elements of the statemachine, we can create and start the sate machine itself:

using TIM.Tracing;
...
Machine<ILampControl, LampStates, LampContext> machine = new ("Lamp",  new LampStateOn(), new LampStateOff());
machine.Options.SetTraceHandler((x) => Debug.WriteLine(x.ToString()));
ILampControl LampControl = machine.Run(LampStates.Off, new LampContext());

By added the using 'TIM.Tracing'you can activate tracing on the state machine by setting a trace handler. In the Machine constructor you give the machine a name, which is used in the traces, and you specify the states of this state machine. By calling Run() you start the statemachine in the specified state with the given context.

For the code of this example and more, please refer to the Samples

<p align="right">(<a href="#readme-top">back to top</a>)</p>

Contributing

If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". Don't forget to give the project a star! Thanks again!

  1. Fork the Project
  2. Create your Feature Branch (git checkout -b feature/AmazingFeature)
  3. Commit your Changes (git commit -m 'Add some AmazingFeature')
  4. Push to the Branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

<p align="right">(<a href="#readme-top">back to top</a>)</p>

License

Distributed under the MIT License. See LICENSE.txt for more information.

<p align="right">(<a href="#readme-top">back to top</a>)</p>

Contact

Jules Verhoeven - j-mail@kpnmail.nl.com

Project Link: https://github.com/JulesVerhoeven/TIM

<p align="right">(<a href="#readme-top">back to top</a>)</p>

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net6.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
2.0.0 288 1/3/2023
1.1.0-beta 758 8/20/2017