TIM 2.0.1

dotnet add package TIM --version 2.0.1
                    
NuGet\Install-Package TIM -Version 2.0.1
                    
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.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="TIM" Version="2.0.1" />
                    
Directory.Packages.props
<PackageReference Include="TIM" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add TIM --version 2.0.1
                    
#r "nuget: TIM, 2.0.1"
                    
#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.
#:package TIM@2.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=TIM&version=2.0.1
                    
Install as a Cake Addin
#tool nuget:?package=TIM&version=2.0.1
                    
Install as a Cake Tool

<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

Implementation guidelines
  • The state machine should always be response. This means that you should not perform any lengthy calculations, waits or sleeps within the trigger implementations. Instead, create a task and use Call(...) to handle it.
  • Do not access the context outside of the states. TIM makes sure that all calls to the states are synchronized. If you acces the context from outside the statemachine you are likely to get concurrency issues.
  • Take care that all callbacks, actions, functions, events, invokes etc. are executed asynchronously from the state machine. If the trigger interface gets called synchronously from inside any of these you will either get an exception or a deadlock! For an example of an event implementation, see the Elevator sample.
  • Do not use asyn/await in the trigger implementation, this will lead to some unexpected results. Instead, use the Call() method.
  • Normally, you would define an enum as key for the states. But, if you expect that the statemachine will get extended elsewhere, use strings. You can define the strings used in the statemachine as constants.
  • TIM does not explicitely support sub-statemachines, but you can easily create them by storing the keys of the state(s) you want to return to in the context. You then return to this state from inside a trigger with a GoTo() to key in the context. There is an example of this in the Elevator sample. (When Jamming the elevator.)

<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 is compatible.  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.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.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.
  • net8.0

    • No dependencies.
  • net9.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.1 485 10/28/2025
2.0.0 660 1/3/2023
1.1.0-beta 1,097 8/20/2017