Skip to content

Instantly share code, notes, and snippets.

@josellm
Last active January 19, 2022 13:19
Show Gist options
  • Save josellm/95b04a3e1c45a4c0fb01a0186865a109 to your computer and use it in GitHub Desktop.
Save josellm/95b04a3e1c45a4c0fb01a0186865a109 to your computer and use it in GitHub Desktop.
Reusing stateless state machine for different instances
using Stateless;
using System;
using System.Collections.Generic;
using System.Linq;
namespace WorkFlow {
public class MyStateMachine<T, TTrigger> {
private readonly static object locker = new object();
private readonly T _element;
private readonly StateMachine<StateEntity<T, TTrigger>, string> _machine;
private readonly static Dictionary<string, StateMachine<StateEntity<T, TTrigger>, string>> internalMachines = new Dictionary<string, StateMachine<StateEntity<T, TTrigger>, string>>();
public MyStateMachine(T element, string initialState, string machineName) {
_element = element;
State = initialState;
lock (locker) {
if (!internalMachines.TryGetValue(machineName, out _machine)) {
//Create Stateless StateMachine
var state = new StateEntity<T, TTrigger>();
_machine = new StateMachine<StateEntity<T, TTrigger>, string>(() => state, (stateEntity) => stateEntity.StateMachine.State = stateEntity.State);
//Configure states
ConfigMachine(machineName);
internalMachines.Add(machineName, _machine);
}
}
}
public T Element { get { return _element; } }
public string State { get; internal set; }
public bool Allow(string triggerName, TTrigger triggerParam) {
lock (locker) {
try {
SetMachineState();
return _machine.CanFire(new StateMachine<StateEntity<T, TTrigger>, string>.TriggerWithParameters<TriggerEntity<T, TTrigger>>(triggerName), new TriggerEntity<T, TTrigger> { StateMachine = this, TriggerParam = triggerParam });
} catch (Exception ex) {
Logger.Error(@"StatelessStateMachine::Allow", ex);
return false;
}
}
}
public bool Fire(string triggerName, TTrigger triggerParam) {
if (!Allow(triggerName, triggerParam))
return false;
lock (locker) {
SetMachineState();
_machine.Fire(new StateMachine<StateEntity<T, TTrigger>, string>.TriggerWithParameters<TriggerEntity<T, TTrigger>>(triggerName), new TriggerEntity<T, TTrigger> { StateMachine = this, TriggerParam = triggerParam });
return true;
}
}
private void SetMachineState() {
_machine.State.State = State;
_machine.State.StateMachine = this;
}
private void ConfigMachine(string machineName) {
//State machine configuration example
_machine.Configure(new StateEntity<T, TTrigger> { StateMachine = this, State = "State1" })
.OnEntry(tran => Logger.Debug(@"Executing action on entry to {1} from {0} by trigger {2}", tran.Source.State, tran.Destination.State, tran.Trigger))
.PermitDynamicIf(
new StateMachine<StateEntity<T, TTrigger>, string>.TriggerWithParameters<TriggerEntity<T, TTrigger>>("Trigger1"),
trigger => {
//Custom stuff
return new StateEntity<T, TTrigger> { StateMachine = trigger.StateMachine, State = "State2" };
},
trigger => CustomChecks(trigger));
_machine.Configure(new StateEntity<T, TTrigger> { StateMachine = this, State = "State2" });
}
private bool CustomChecks(TriggerEntity<T, TTrigger> trigger) {
//I have access to current trigger.StateMachine
return true;
}
}
public class StateEntity<T, TTrigger> {
public string State { get; internal set; }
public MyStateMachine<T, TTrigger> StateMachine { get; set; }
public override bool Equals(object obj) {
var state = obj as StateEntity<T, TTrigger>;
return state != null && state.State == this.State;
}
public override int GetHashCode() {
return State != null? State.GetHashCode() : 0;
}
}
internal class TriggerEntity<T, TTrigger> {
public MyStateMachine<T, TTrigger> StateMachine { get; set; }
public TTrigger TriggerParam { get; set; }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment