Skip to content

Commit

Permalink
initial draft
Browse files Browse the repository at this point in the history
  • Loading branch information
MovGP0 committed Dec 14, 2014
1 parent 06d0d0d commit 172424b
Show file tree
Hide file tree
Showing 16 changed files with 433 additions and 2 deletions.
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,14 @@
State-Machines
==============
# Active State Machine Example
Example Implementation of the Active State Machine pattern in .NET.

An Active State Machine is a state machine that performs transitions on a different thread as the code calling the state machine.
This allows for multiple threads calling it via a synchronization queue.

## Implementation Notes
* This implementation takes use of the [Task Parallel Library](http://msdn.microsoft.com/en-us/library/dd460717.aspx) (TPL) to allow input from multiple threads.
* The state machine handles messages internally using [Reactive Extensions](https://rx.codeplex.com/) (Rx).
* [Fody](https://github.com/Fody/Fody) is used for Aspects
* [NLog](http://nlog-project.org/) is used for logging

## References
1. [How to: Implement a Producer-Consumer Dataflow Pattern](http://msdn.microsoft.com/en-us/library/hh228601%28v=vs.110%29.aspx)
110 changes: 110 additions & 0 deletions State-Machines/ActiveStateMachine/ActiveStateMachine.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{8C055B35-5FC5-4F9D-92C4-AFCBC2F43EF7}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ActiveStateMachine</RootNamespace>
<AssemblyName>ActiveStateMachine</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<NuGetPackageImportStamp>f16ffc44</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<CodeAnalysisRuleSet>ManagedMinimumRules.ruleset</CodeAnalysisRuleSet>
<RunCodeAnalysis>true</RunCodeAnalysis>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Anotar.NLog">
<HintPath>..\packages\Anotar.NLog.Fody.2.13.0\Lib\Anotar.NLog.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="NLog">
<HintPath>..\packages\NLog.3.1.0.0\lib\net45\NLog.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Reactive.Core">
<HintPath>..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll</HintPath>
</Reference>
<Reference Include="System.Reactive.Interfaces">
<HintPath>..\packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll</HintPath>
</Reference>
<Reference Include="System.Reactive.Linq">
<HintPath>..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll</HintPath>
</Reference>
<Reference Include="System.Reactive.PlatformServices">
<HintPath>..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Dataflow, Version=4.5.24.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.Tpl.Dataflow.4.5.24\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll</HintPath>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="ToString">
<HintPath>..\packages\ToString.Fody.1.7.0.0\Lib\portable-net4+sl4+wp7+win8+MonoAndroid16+MonoTouch40\ToString.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Logging\MessageLogger.cs" />
<Compile Include="Messages\MessageReceiver.cs" />
<Compile Include="StateMachine.cs" />
<Compile Include="Messages\IMessage.cs" />
<Compile Include="StateMachineState.cs" />
<Compile Include="States\State.cs" />
<Compile Include="Transitions\TransitionAction.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Messages\StateMachineCommandMessage.cs" />
<Compile Include="Messages\StateMachineExternalMessage.cs" />
<Compile Include="Messages\StateMachineInfo.cs" />
<Compile Include="Messages\StateMachineMessage.cs" />
<Compile Include="Messages\StateMachineNotificationMessage.cs" />
<Compile Include="Messages\StateMachineSystemMessage.cs" />
<Compile Include="Transitions\TransitionPrecondition.cs" />
<Compile Include="Transitions\Transition.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Content Include="FodyWeavers.xml" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\packages\Fody.1.26.2\build\Fody.targets" Condition="Exists('..\packages\Fody.1.26.2\build\Fody.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\Fody.1.26.2\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Fody.1.26.2\build\Fody.targets'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
5 changes: 5 additions & 0 deletions State-Machines/ActiveStateMachine/FodyWeavers.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Weavers>
<Anotar.NLog />
<ToString />
</Weavers>
43 changes: 43 additions & 0 deletions State-Machines/ActiveStateMachine/Logging/MessageLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System;
using ActiveStateMachine.Messages;
using Anotar.NLog;

namespace ActiveStateMachine.Logging
{
public sealed class MessageLogger : IObserver<IMessage>
{
public MessageLogger(string name = "")
{
Name = name;
}

private string Name { get; }

public void OnNext(IMessage value)
{
var message = string.IsNullOrWhiteSpace(Name)
? string.Format("Received message: {0}", value)
: string.Format("{0}: Received message: {1}", Name, value);

LogTo.Trace(message);
}

public void OnError(Exception error)
{
var message = string.IsNullOrWhiteSpace(Name)
? "Encountered error."
: string.Format("{0}: Encountered error.", Name);

LogTo.ErrorException(message, error);
}

public void OnCompleted()
{
var message = string.IsNullOrWhiteSpace(Name)
? "Source completed."
: string.Format("{0}: Source completed.", Name);

LogTo.Trace(message);
}
}
}
11 changes: 11 additions & 0 deletions State-Machines/ActiveStateMachine/Messages/IMessage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;

namespace ActiveStateMachine.Messages
{
public interface IMessage
{
Guid Id { get; }
DateTime Timestamp { get; }
Version Version { get; }
}
}
30 changes: 30 additions & 0 deletions State-Machines/ActiveStateMachine/Messages/MessageReceiver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;

namespace ActiveStateMachine.Messages
{
public sealed class MessageReceiver : IObservable<IMessage>
{
private List<IObserver<IMessage>> Observers { get; } = new List<IObserver<IMessage>>();

public async Task ReceiveAsync(ISourceBlock<IMessage> source)
{
while (await source.OutputAvailableAsync())
{
var message = source.Receive();
Observers.ForEach(observer => observer.OnNext(message));
}

Observers.ForEach(observer => observer.OnCompleted());
}

public IDisposable Subscribe(IObserver<IMessage> observer)
{
Observers.Add(observer);
return Disposable.Create(() => Observers.Remove(observer));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;

namespace ActiveStateMachine.Messages
{
[ToString]
public sealed class StateMachineCommandMessage : StateMachineMessage
{
public StateMachineCommandMessage(StateMachineInfo payload)
: base(new Version(1,0))
{
Payload = payload;
}

public StateMachineInfo Payload { get; }
}
}
19 changes: 19 additions & 0 deletions State-Machines/ActiveStateMachine/Messages/StateMachineInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace ActiveStateMachine.Messages
{
[ToString]
public sealed class StateMachineInfo
{
public StateMachineInfo(string name, string eventInfo, string source, string target)
{
Name = name;
EventInfo = eventInfo;
Source = source;
Target = target;
}

public string Name { get; }
public string EventInfo { get; }
public string Source { get; }
public string Target { get; }
}
}
20 changes: 20 additions & 0 deletions State-Machines/ActiveStateMachine/Messages/StateMachineMessage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;

namespace ActiveStateMachine.Messages
{
public abstract class StateMachineMessage : IMessage
{
protected StateMachineMessage(Version version)
{
Version = version;
Timestamp = DateTime.UtcNow;
Id = new Guid();
}

public Guid Id { get; }

public DateTime Timestamp { get; }

public Version Version { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;

namespace ActiveStateMachine.Messages
{
[ToString]
public sealed class StateMachineSystemMessage : StateMachineMessage
{
public StateMachineSystemMessage(StateMachineInfo payload)
: base(new Version(1,0))
{
Payload = payload;
}

public StateMachineInfo Payload { get; }
}
}
36 changes: 36 additions & 0 deletions State-Machines/ActiveStateMachine/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ActiveStateMachine")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ActiveStateMachine")]
[assembly: AssemblyCopyright("Copyright © 2014")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("8c055b35-5fc5-4f9d-92c4-afcbc2f43ef7")]

// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
51 changes: 51 additions & 0 deletions State-Machines/ActiveStateMachine/StateMachine.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using ActiveStateMachine.Messages;
using ActiveStateMachine.States;
using Anotar.NLog;

namespace ActiveStateMachine
{
public sealed class StateMachine : IObserver<IMessage>
{
public IEnumerable<State> PossibleStates { get; }
public State CurrentState => StateHistory.Last();
public IEnumerable<State> StateHistory { get; } = new List<State>();
public IEnumerable<IMessage> MessageHistory { get; } = new List<IMessage>();
public StateMachineState State { get; private set; }

public StateMachine(IEnumerable<State> possibleStates)
{
PossibleStates = possibleStates;
State = StateMachineState.Initialized;
}

[LogToErrorOnException]
public void Start()
{
if (State == StateMachineState.Initialized)
State = StateMachineState.Running;
}

[LogToErrorOnException]
public void OnNext(IMessage message)
{
if(State != StateMachineState.Running) return;

throw new NotImplementedException();
}

public void OnError(Exception error)
{
State = StateMachineState.Stopped;
LogTo.InfoException("Stopped because of an exception", error);
}

[LogToErrorOnException]
public void OnCompleted()
{
State = StateMachineState.Stopped;
}
}
}
Loading

0 comments on commit 172424b

Please sign in to comment.