Last active
November 15, 2016 01:41
-
-
Save joonhwan/ae945e6e64e70184e7e453a3e8de4506 to your computer and use it in GitHub Desktop.
Context Owned StateMachine usage proposal. (using stateless library)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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