diff --git a/Implab.Test/Implab.Format.Test/JsonTests.cs b/Implab.Test/Implab.Format.Test/JsonTests.cs --- a/Implab.Test/Implab.Format.Test/JsonTests.cs +++ b/Implab.Test/Implab.Format.Test/JsonTests.cs @@ -10,7 +10,7 @@ namespace Implab.Format.Test { public void TestScannerValidTokens() { using (var scanner = new JSONScanner(@"9123, -123, 0, 0.1, -0.2, -0.1e3, 1.3E-3, ""some \t\n\u0020 text"", literal []{}:")) { - Tuple[] expexted = new [] { + Tuple[] expexted = { new Tuple(JsonTokenType.Number, 9123d), new Tuple(JsonTokenType.ValueSeparator, ", "), new Tuple(JsonTokenType.Number, -123d), diff --git a/Implab/Components/ExecutionState.cs b/Implab/Components/ExecutionState.cs --- a/Implab/Components/ExecutionState.cs +++ b/Implab/Components/ExecutionState.cs @@ -1,14 +1,24 @@ namespace Implab.Components { public enum ExecutionState { - Reserved = 0, - Uninitialized, + Undefined = 0, + + Created, + + Initializing, + Ready, + Starting, + Running, + Stopping, - Stopped, + + Failed, + Disposed, - Failed + + Last = Disposed } } \ No newline at end of file diff --git a/Implab/Components/IInitializable.cs b/Implab/Components/IInitializable.cs --- a/Implab/Components/IInitializable.cs +++ b/Implab/Components/IInitializable.cs @@ -11,7 +11,7 @@ namespace Implab.Components { /// Completes initialization. /// /// - /// Normally virtual shouldn't be called from the constructor, due to the incomplete object state, but + /// Normally virtual methods shouldn't be called from the constructor, due to the incomplete object state, but /// they can be called from this method. This method is also usefull when we constructing a complex grpah /// of components where cyclic references may take place. /// diff --git a/Implab/Components/RunnableComponent.cs b/Implab/Components/RunnableComponent.cs --- a/Implab/Components/RunnableComponent.cs +++ b/Implab/Components/RunnableComponent.cs @@ -3,22 +3,149 @@ 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( + } + } + ); - - IPromise m_pending; - Exception m_lastError; + return Safe.InvokePromise(action).Then( + Success, + Fail + ); + } - protected RunnableComponent(bool initialized) { - + void AddPending(IPromise result) { + } + #region IInitializable implementation public void Init() { - + Invoke(Commands.Init, OnInitialize); + } + + protected virtual void OnInitialize() { } #endregion @@ -26,7 +153,17 @@ namespace Implab.Components { #region IRunnable implementation public IPromise Start() { - throw new NotImplementedException(); + Move(Commands.Start); + + return Safe.InvokePromise(OnStart).Then( + () => { + Move(Commands.Ok); + Run(); + }, + () => { + Move(Commands.Fail); + } + ); } protected virtual IPromise OnStart() {