Skip to content

Instantly share code, notes, and snippets.

@joonhwan
Last active November 15, 2016 01:41
Show Gist options
  • Save joonhwan/ae945e6e64e70184e7e453a3e8de4506 to your computer and use it in GitHub Desktop.
Save joonhwan/ae945e6e64e70184e7e453a3e8de4506 to your computer and use it in GitHub Desktop.
Context Owned StateMachine usage proposal. (using stateless library)
using System;
using Stateless;
namespace HelloStatemachine
{
public interface IHaveContext<T>
{
T Context { get; set; }
};
public interface ITriggerShooter<in TContext>
{
void FireOn(TContext instance);
}
public class ContextOwnedStateMachine<TState, TTrigger, TContext>
: IHaveContext<TContext>
where TContext : class
{
// can this help threading issue?
private readonly ThreadLocal<TContext> _threadLocalContext = new ThreadLocal<TContext>();
// did hide context setter from outside.
TContext IHaveContext<TContext>.Context
{
get { return _threadLocalContext.Value; }
set { _threadLocalContext.Value = value; }
}
private StateMachine<TState, TTrigger> _machine;
protected StateMachine<TState, TTrigger> StateMachine
{
get { return _machine; }
}
public ContextOwnedStateMachine(Func<TContext, TState> stateGetter, Action<TContext, TState> stateSetter)
{
var contextOnwer = (IHaveContext<TContext>) this;
Func<TState> getter = () => stateGetter(contextOnwer.Context);
Action<TState> setter = state => stateSetter(contextOnwer.Context, state);
_machine = new StateMachine<TState, TTrigger>(getter, setter);
}
public ITriggerShooter<TContext> CreateTriggerShooter(TTrigger trigger)
{
return new TriggerShooter(this, _machine, trigger);
}
class TriggerShooter : ITriggerShooter<TContext>
{
private readonly TTrigger _trigger;
private readonly StateMachine<TState, TTrigger> _sm;
private readonly IHaveContext<TContext> _contextOwner;
public TriggerShooter(IHaveContext<TContext> contextOwner, StateMachine<TState, TTrigger> sm,
TTrigger trigger)
{
_contextOwner = contextOwner;
_sm = sm;
_trigger = trigger;
}
public void FireOn(TContext context)
{
_contextOwner.Context = context;
_sm.Fire(_trigger);
}
}
public StateConfiguration Configure(TState state)
{
return new StateConfiguration(this, _machine.Configure(state));
}
public class StateConfiguration
{
private StateMachine<TState, TTrigger>.StateConfiguration _internalConfigure;
private IHaveContext<TContext> _contextOwner;
public StateConfiguration(IHaveContext<TContext> contextOwner, StateMachine<TState, TTrigger>.StateConfiguration configure)
{
_contextOwner = contextOwner;
_internalConfigure = configure;
}
public StateConfiguration OnEntry(Action<TContext> action)
{
_internalConfigure.OnEntry(() => action(_contextOwner.Context));
return this;
}
public StateConfiguration Permit(TTrigger trigger, TState state)
{
_internalConfigure.Permit(trigger, state);
return this;
}
public StateConfiguration Handle(TTrigger trigger, Action<TContext> handler)
{
_internalConfigure.InternalTransition(trigger, () => handler(_contextOwner.Context));
return this;
}
}
}
}
public class JobDto
{
public string State { get; set; }
public string Name { get; set; }
}
public class Tester
{
enum JobCommand { Start, Stop }
enum JobState { Running, Stopped }
public static void Test()
{
var machine = new ContextOwnedStateMachine<JobState, JobCommand, JobDto>(
job =>
{
switch (job.State)
{
case "Executing":
return JobState.Running;
case "Finished":
return JobState.Stopped;
default:
throw new ArgumentOutOfRangeException();
}
}, (job, state) =>
{
switch (state)
{
case JobState.Running:
job.State = "Executing";
break;
case JobState.Stopped:
job.State = "Finished";
break;
default:
throw new ArgumentOutOfRangeException();
}
});
machine.Configure(JobState.Stopped)
.OnEntry(job => Print($"Now Stopped {job.Name}"))
.Permit(JobCommand.Start, JobState.Running)
.Handle(JobCommand.Stop, context => Print($"{context.Name} : Ignore Stop while Stopped"));
machine.Configure(JobState.Running)
.OnEntry(job => Print($"{job.Name} : Now Running"))
.Permit(JobCommand.Stop, JobState.Stopped)
.Handle(JobCommand.Start, job => Print($"{job.Name} : Ignore Start while Running"));
var startEvent = machine.CreateTriggerShooter(JobCommand.Start);
var stopEvent = machine.CreateTriggerShooter(JobCommand.Stop);
// for any given job instance..
var simpleJob = new JobDto();
// you can fire start event ...
startEvent.FireOn(simpleJob);
// or you can fire stop event...
stopEvent.FireOn(simpleJob);
}
private static void Print(string s)
{
Console.WriteLine(s);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment