using System; using Implab.Formats; namespace Implab.Components { public class RunnableComponent : Disposable, IRunnable, IInitializable { enum Commands { Ok = 0, Fail, Init, Start, Stop, Dispose, Last = Dispose } class StateMachine { static readonly ExecutionState[,] _transitions; static StateMachine() { _transitions = new ExecutionState[(int)ExecutionState.Last + 1, (int)Commands.Last + 1]; Edge(ExecutionState.Created, ExecutionState.Ready, Commands.Ok); Edge(ExecutionState.Created, ExecutionState.Failed, Commands.Fail); Edge(ExecutionState.Ready, ExecutionState.Starting, Commands.Start); Edge(ExecutionState.Ready, ExecutionState.Disposed, Commands.Dispose); Edge(ExecutionState.Starting, ExecutionState.Running, Commands.Ok); Edge(ExecutionState.Starting, ExecutionState.Failed, Commands.Fail); Edge(ExecutionState.Starting, ExecutionState.Stopping, Commands.Stop); Edge(ExecutionState.Starting, ExecutionState.Disposed, Commands.Dispose); Edge(ExecutionState.Running, ExecutionState.Failed, Commands.Fail); Edge(ExecutionState.Running, ExecutionState.Stopping, Commands.Stop); Edge(ExecutionState.Running, ExecutionState.Disposed, Commands.Dispose); Edge(ExecutionState.Stopping, ExecutionState.Failed, Commands.Fail); Edge(ExecutionState.Stopping, ExecutionState.Disposed, Commands.Ok); Edge(ExecutionState.Stopping, ExecutionState.Disposed, Commands.Dispose); } static void Edge(ExecutionState s1, ExecutionState s2, Commands cmd) { _transitions[(int)s1, (int)cmd] = s2; } public ExecutionState State { get; private set; } public StateMachine(ExecutionState initial) { State = initial; } public bool Move(Commands cmd) { var next = _transitions[(int)State, (int)cmd]; if (next == ExecutionState.Undefined) return false; State = next; return true; } } IPromise m_pending; Exception m_lastError; readonly StateMachine m_stateMachine; protected RunnableComponent(bool initialized) { m_stateMachine = new StateMachine(initialized ? ExecutionState.Ready : ExecutionState.Created); } void ThrowInvalidCommand(Commands cmd) { throw new InvalidOperationException(String.Format("Commnd {0} is not allowed in the state {1}", cmd, m_stateMachine.State)); } protected void Move(Commands cmd) { lock (m_stateMachine) if (!m_stateMachine.Move(cmd)) ThrowInvalidCommand(cmd); } protected void Fail(Exception err) { lock (m_stateMachine) { if (!m_stateMachine.Move(Commands.Fail)) ThrowInvalidCommand(Commands.Fail); m_lastError = err; } } protected void Success() { Move(Commands.Ok); } protected void Invoke(Commands cmd, Action action) { Move(cmd); try { action(); Move(Commands.Ok); } catch (Exception err) { Fail(err); throw; } } protected IPromise InvokeAsync(Commands cmd, Func action) { Move(cmd); var medium = new Promise(); IPromise promise = null; promise = medium.Then( () => { lock(m_stateMachine) { if (m_pending == promise) { m_pending = null; Move(Commands.Ok); } } }, e => { if (m_pending == promise) { m_pending = null; Fail( } } ); return Safe.InvokePromise(action).Then( Success, Fail ); } void AddPending(IPromise result) { } #region IInitializable implementation public void Init() { Invoke(Commands.Init, OnInitialize); } protected virtual void OnInitialize() { } #endregion #region IRunnable implementation public IPromise Start() { Move(Commands.Start); return Safe.InvokePromise(OnStart).Then( () => { Move(Commands.Ok); Run(); }, () => { Move(Commands.Fail); } ); } protected virtual IPromise OnStart() { return Promise.SUCCESS; } protected virtual void Run() { } public IPromise Stop() { throw new NotImplementedException(); } public ExecutionState State { get { throw new NotImplementedException(); } } public Exception LastError { get { throw new NotImplementedException(); } } #endregion } }