diff --git a/StateMachine.NET.TestConsole/Program.cs b/StateMachine.NET.TestConsole/Program.cs index e488858..a25f07c 100644 --- a/StateMachine.NET.TestConsole/Program.cs +++ b/StateMachine.NET.TestConsole/Program.cs @@ -33,10 +33,14 @@ static void Main(string[] args) } } + + var hr = context.shutdown(); + Console.WriteLine($"Context.shutdown() returns {hr}.\nKey in [Enter] to exit."); + Console.ReadLine(); } } - class Context : tsm_NET.Generic.Context + class Context : tsm_NET.Generic.AsyncContext { } diff --git a/StateMachine.NET.UnitTest/Class1.cs b/StateMachine.NET.UnitTest/Class1.cs index 7dc11d2..b28451f 100644 --- a/StateMachine.NET.UnitTest/Class1.cs +++ b/StateMachine.NET.UnitTest/Class1.cs @@ -1,33 +1,30 @@ using NSubstitute; using NUnit.Framework; using System; -using System.Diagnostics; using System.Threading; using tsm_NET.Generic; namespace StateMachine.NET.UnitTest.Generic { - public class Context : Context + [TestFixture] + public class TestCase { - public Context() : base(true) { } - public Context(bool isAsync) : base(isAsync) { } - } + public class Context : Context + { + } - public class Event : Event - { - public static Event Null { get; } = null; - } + public class Event : Event + { + public static Event Null { get; } = null; + } - public class State : State - { - public static State Null { get; } = null; - } + public class State : State + { + public static State Null { get; } = null; + } - [TestFixture] - public class GenericTestCase - { [Test] - public void SyncContextTest() + public void BasicTest() { var mockEvent = Substitute.For(); var mockInitialState = Substitute.For(); @@ -35,7 +32,7 @@ public void SyncContextTest() var mockStateMonitor = Substitute.For>(); // Create synchronous Context object. - var c = new Context(false); + var c = new Context(); c.StateMonitor = mockStateMonitor; Assert.That(c.CurrentState, Is.EqualTo(null), "Context has no initial state when created."); @@ -89,11 +86,31 @@ public void SyncContextTest() mockStateMonitor.DidNotReceive().onIdle(Arg.Any()); mockStateMonitor.DidNotReceive().onWorkerThreadExit(Arg.Any(), Arg.Any()); } + } + + [TestFixture] + public class AsyncTestCase + { + public class Context : AsyncContext + { + // StateMachine should run on managed thread to test on NUnit. + public Context() : base(true) { } + } + + public class Event : Event + { + public static Event Null { get; } = null; + } + + public class State : State + { + public static State Null { get; } = null; + } [Test] public void BasicTest() { - var mockEvent = Substitute.For(); + var mockEvent = Substitute.For(); var mockInitialState = Substitute.For(); var mockNextState = Substitute.For(); var mockStateMonitor = Substitute.For>(); @@ -122,7 +139,7 @@ public void BasicTest() // Shutdown mockNextState.IsExitCalledOnShutdown = true; Assume.That(c.shutdown(TimeSpan.FromSeconds(1)), Is.EqualTo(HResult.Ok)); - Thread.Sleep(1000); + Thread.Sleep(100); // Check calls to methods of State. Received.InOrder(() => @@ -143,8 +160,6 @@ public void BasicTest() // Check calls to methods of IStateMonitor. Received.InOrder(() => { - mockStateMonitor.Received() - .onEventTriggered(Arg.Is(c), Arg.Is(mockEvent)); mockStateMonitor.Received() .onEventHandling(Arg.Is(c), Arg.Is(mockEvent), Arg.Is(mockInitialState)); mockStateMonitor.Received() @@ -157,6 +172,9 @@ public void BasicTest() // onStateChanged() caused by Context.setup() might be called before or after onEventTriggerd(). mockStateMonitor.Received() .onStateChanged(Arg.Is(c), Arg.Is(Event.Null), Arg.Is(State.Null), Arg.Is(mockInitialState)); + // onEventTriggered() might be called before or after onEventHandling(). + mockStateMonitor.Received() + .onEventTriggered(Arg.Is(c), Arg.Is(mockEvent)); } } } diff --git a/StateMachine.NET.UnitTest/Class2.cs b/StateMachine.NET.UnitTest/Class2.cs index b9f2b24..9110967 100644 --- a/StateMachine.NET.UnitTest/Class2.cs +++ b/StateMachine.NET.UnitTest/Class2.cs @@ -1,13 +1,13 @@ using NSubstitute; using NUnit.Framework; -using System.Diagnostics; +using System; using System.Threading; using tsm_NET; namespace StateMachine.NET.UnitTest { [TestFixture] - public class TestCase + public class AsyncTestCase { [Test] public void BasicTest() @@ -17,7 +17,8 @@ public void BasicTest() var mockNextState = Substitute.For(); var mockStateMonitor = Substitute.For(); - var c = new Context(); + // StateMachine should run on managed thread to test on NUnit. + var c = new AsyncContext(true); c.StateMonitor = mockStateMonitor; Assert.That(c.CurrentState, Is.EqualTo(null), "Context has no initial state when created."); @@ -26,7 +27,7 @@ public void BasicTest() .handleEvent(Arg.Is(c), Arg.Is(mockEvent), ref Arg.Is((State)null)) .Returns(x => { - Trace.WriteLine($"{mockInitialState.GetType()}.handleEvent({x[0]}) is called."); + Console.WriteLine($"{mockInitialState.GetType()}.handleEvent({x[0]}) is called."); x[2] = mockNextState; return HResult.Ok; }); @@ -62,8 +63,6 @@ public void BasicTest() // Check calls to methods of IStateMonitor. Received.InOrder(() => { - mockStateMonitor.Received() - .onEventTriggered(Arg.Is(c), Arg.Is(mockEvent)); mockStateMonitor.Received() .onEventHandling(Arg.Is(c), Arg.Is(mockEvent), Arg.Is(mockInitialState)); mockStateMonitor.Received() @@ -76,6 +75,9 @@ public void BasicTest() // onStateChanged() caused by Context.setup() might be called before or after onEventTriggerd(). mockStateMonitor.Received() .onStateChanged(Arg.Is(c), Arg.Is((Event)null), Arg.Is((State)null), Arg.Is(mockInitialState)); + // onEventTriggered() might be called before or after onEventHandling(). + mockStateMonitor.Received() + .onEventTriggered(Arg.Is(c), Arg.Is(mockEvent)); } } } diff --git a/StateMachine.NET.UnitTest/StateMachine.NET.UnitTest.csproj b/StateMachine.NET.UnitTest/StateMachine.NET.UnitTest.csproj index c757e63..1304aad 100644 --- a/StateMachine.NET.UnitTest/StateMachine.NET.UnitTest.csproj +++ b/StateMachine.NET.UnitTest/StateMachine.NET.UnitTest.csproj @@ -88,6 +88,7 @@ - xcopy /D /Y "$(SolutionDir)packages\NUnit.ConsoleRunner.3.10.0\tools\*.*" "$(TargetDir)" + + \ No newline at end of file diff --git a/StateMachine.NET/GenericObject.cpp b/StateMachine.NET/GenericObject.cpp index f87a4d5..6f4127b 100644 --- a/StateMachine.NET/GenericObject.cpp +++ b/StateMachine.NET/GenericObject.cpp @@ -66,38 +66,38 @@ namespace Generic } generic - HRESULT State::handleEventCallback(tsm::IContext* context, tsm::IEvent* event, tsm::IState** nextState) + tsm_NET::HResult State::handleEvent(tsm_NET::Context^ context, tsm_NET::Event^ event, tsm_NET::State^% nextState) { S _nextState; - auto hr = handleEvent((C)getManaged((native::Context*)context), (E)getManaged((native::Event*)event), _nextState); + auto hr = handleEvent((C)context, (E)event, _nextState); if(_nextState) { - *nextState = _nextState->get(); + nextState = _nextState; } - return (HRESULT)hr; + return (tsm_NET::HResult)hr; } generic - HRESULT State::entryCallback(tsm::IContext* context, tsm::IEvent* event, tsm::IState* previousState) + tsm_NET::HResult State::entry(tsm_NET::Context^ context, tsm_NET::Event^ event, tsm_NET::State^ previousState) { - return (HRESULT)entry((C)getManaged((native::Context*)context), (E)getManaged((native::Event*)event), (S)getManaged((native::State*)previousState)); + return (tsm_NET::HResult)entry((C)context, (E)event, (S)previousState); } generic - HRESULT State::exitCallback(tsm::IContext* context, tsm::IEvent* event, tsm::IState* nextState) + tsm_NET::HResult State::exit(tsm_NET::Context^ context, tsm_NET::Event^ event, tsm_NET::State^ nextState) { - return (HRESULT)exit((C)getManaged((native::Context*)context), (E)getManaged((native::Event*)event), (S)getManaged((native::State*)nextState)); + return (tsm_NET::HResult)exit((C)context, (E)event, (S)nextState); } generic - HRESULT Event::preHandleCallback(tsm::IContext* context) + tsm_NET::HResult Event::preHandle(tsm_NET::Context^ context) { - return (HRESULT)preHandle((C)getManaged((native::Context*)context)); + return (tsm_NET::HResult)preHandle((C)context); } generic - HRESULT Event::postHandleCallback(tsm::IContext* context, HRESULT hr) + tsm_NET::HResult Event::postHandle(tsm_NET::Context^ context, tsm_NET::HResult hr) { - return (HRESULT)postHandle((C)getManaged((native::Context*)context), (HResult)hr); + return (tsm_NET::HResult)postHandle((C)context, (HResult)hr); } } } diff --git a/StateMachine.NET/GenericObject.h b/StateMachine.NET/GenericObject.h index 05ee1fa..2f41be4 100644 --- a/StateMachine.NET/GenericObject.h +++ b/StateMachine.NET/GenericObject.h @@ -43,9 +43,11 @@ public ref class StateMonitorCaller : public tsm_NET::StateMonitorCaller generic public ref class Context : public tsm_NET::Context { +protected: + Context(bool isAsync, bool useNativeThread) : tsm_NET::Context(isAsync, useNativeThread), m_stateMonitor(nullptr) {} + public: - Context() : tsm_NET::Context(true), m_stateMonitor(nullptr) {} - Context(bool isAsync ) : tsm_NET::Context(isAsync), m_stateMonitor(nullptr) {} + Context() : tsm_NET::Context(false, false), m_stateMonitor(nullptr) {} virtual ~Context() {} HResult setup(S initialState, E event) { return (HResult)tsm_NET::Context::setup((tsm_NET::State^)initialState, (tsm_NET::Event^)event); } @@ -72,6 +74,14 @@ public ref class Context : public tsm_NET::Context StateMonitorCaller^ m_stateMonitorCaller; }; +generic +public ref class AsyncContext : public Context +{ +public: + AsyncContext() : Context(true, false) {} + AsyncContext(bool useNativeThread) : Context(true, useNativeThread) {} +}; + generic where C : tsm_NET::Context where E : tsm_NET::Event @@ -81,7 +91,6 @@ public ref class State : public tsm_NET::State public: State() : tsm_NET::State(nullptr) {} State(S masterState) : tsm_NET::State((tsm_NET::State^)masterState) {} - ~State() {} #pragma region Methods to be implemented by sub class. virtual HResult handleEvent(C context, E event, S% nextState) { return HResult::Ok; } @@ -93,13 +102,10 @@ public ref class State : public tsm_NET::State property S MasterState { S get() { return getMasterState(); } } -// NOTE: Callback methods that is called by native class should be `internal` -// to avoid `System.MissingMethodException` when NUnit runs with NSubstitute. -internal: -#pragma region Methods that call sub class with generic parameters. - virtual HRESULT handleEventCallback(tsm::IContext* context, tsm::IEvent* event, tsm::IState** nextState) override; - virtual HRESULT entryCallback(tsm::IContext* context, tsm::IEvent* event, tsm::IState* previousState) override; - virtual HRESULT exitCallback(tsm::IContext* context, tsm::IEvent* event, tsm::IState* nextState) override; +#pragma region Override methods of tsm_NET::State that call sub class with generic parameters. + virtual tsm_NET::HResult handleEvent(tsm_NET::Context^ context, tsm_NET::Event^ event, tsm_NET::State^% nextState) override sealed; + virtual tsm_NET::HResult entry(tsm_NET::Context^ context, tsm_NET::Event^ event, tsm_NET::State^ previousState) override sealed; + virtual tsm_NET::HResult exit(tsm_NET::Context^ context, tsm_NET::Event^ event, tsm_NET::State^ nextState) override sealed; #pragma endregion }; @@ -108,17 +114,15 @@ generic public ref class Event : public tsm_NET::Event { public: - ~Event() {} #pragma region Methods to be implemented by sub class. virtual HResult preHandle(C context) { return HResult::Ok; } virtual HResult postHandle(C context, HResult hr) { return hr; } #pragma endregion -internal: #pragma region Methods that call sub class with generic parameters. - virtual HRESULT preHandleCallback(tsm::IContext* context) override; - virtual HRESULT postHandleCallback(tsm::IContext* context, HRESULT hr) override; + virtual tsm_NET::HResult preHandle(tsm_NET::Context^ context) override sealed; + virtual tsm_NET::HResult postHandle(tsm_NET::Context^ context, tsm_NET::HResult hr) override sealed; #pragma endregion }; } diff --git a/StateMachine.NET/NativeCallback.h b/StateMachine.NET/NativeCallback.h deleted file mode 100644 index c62cccc..0000000 --- a/StateMachine.NET/NativeCallback.h +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -using System::Runtime::InteropServices::Marshal; - -namespace native -{ -/* - Callback template class. - This class is used to avoid `Cannot pass a GCHandle across AppDomains` exception - when method of managed class is called by native(unmanaged) class in worker thread. - - How to use this class. - - 1. Define delegate(class D), callback signature(class C) and callback method in managed class(class M). - delegate HRESULT XxxDelegate(...); // delegate - typedef HRESULT (__stdcall *XxxCallback)(...); // callback signature - HRESULT xxxCallback(...) { method body } // callback method - - 2. Declare member variable of this class in native class. - Callback m_xxxCallback; - - 3. Initialize the member variable in the constructor of native class. - m_xxxCallback(gcnew XxxDelegate(managedObject, &ManagedClass::xxxCallback)) - - 4. Callback from native class. - m_xxxCallback(...); - - NOTE: - Parameters and return value should be native(unmanaged) type. - - See http://lambert.geek.nz/2007/05/unmanaged-appdomain-callback/. -*/ -template -class Callback -{ -public: - Callback(D^ del) : del(del) { - callback = (C)Marshal::GetFunctionPointerForDelegate(del).ToPointer(); - } - - operator C() { return callback; } - -protected: - gcroot del; - C callback; -}; - -} diff --git a/StateMachine.NET/NativeObjects.cpp b/StateMachine.NET/NativeObjects.cpp index 0f4fc13..6b280ed 100644 --- a/StateMachine.NET/NativeObjects.cpp +++ b/StateMachine.NET/NativeObjects.cpp @@ -14,50 +14,99 @@ struct EventHandle {}; struct TimerHandle {}; } -StateMonitor::StateMonitor(StateMonitor::OwnerType^ owner, - OwnerType::OnIdleCallback onIdleCallback, - OwnerType::OnEventTriggeredCallback onEventTriggeredCallback, - OwnerType::OnEventHandlingCallback onEventHandlingCallback, - OwnerType::OnStateChangedCallback onStateChangedCallback, - OwnerType::OnTimerStartedCallback onTimerStartedCallback, - OwnerType::OnWorkerThreadExitCallback onWorkerThreadExitCallback) - : m_onIdleCallback(onIdleCallback) - , m_onEventTriggeredCallback(onEventTriggeredCallback) - , m_onEventHandlingCallback(onEventHandlingCallback) - , m_onStateChangedCallback(onStateChangedCallback) - , m_onTimerStartedCallback(onTimerStartedCallback) - , m_onWorkerThreadExitCallback(onWorkerThreadExitCallback) +StateMonitor::StateMonitor(StateMonitor::OwnerType^ owner) + : m_owner(owner) { } void StateMonitor::onIdle(tsm::IContext* context) { - m_onIdleCallback(context); + m_owner->onIdleCallback(context); } void StateMonitor::onEventTriggered(tsm::IContext* context, tsm::IEvent* event) { - m_onEventTriggeredCallback(context, event); + m_owner->onEventTriggeredCallback(context, event); } void StateMonitor::onEventHandling(tsm::IContext* context, tsm::IEvent* event, tsm::IState* current) { - m_onEventHandlingCallback(context, event, current); + m_owner->onEventHandlingCallback(context, event, current); } void StateMonitor::onStateChanged(tsm::IContext* context, tsm::IEvent* event, tsm::IState* previous, tsm::IState* next) { - m_onStateChangedCallback(context, event, previous, next); + m_owner->onStateChangedCallback(context, event, previous, next); } void StateMonitor::onTimerStarted(tsm::IContext* context, tsm::IEvent* event) { - m_onTimerStartedCallback(context, event); + m_owner->onTimerStartedCallback(context, event); } void StateMonitor::onWorkerThreadExit(tsm::IContext* context, HRESULT exitCode) { - m_onWorkerThreadExitCallback(context, exitCode); + m_owner->onWorkerThreadExitCallback(context, exitCode); +} + +class AsyncDispatcher; + +/** + * Managed class to dispatch tsm::IAsyncDispatcher::Method(lpParam) in managed worker thread. + */ +ref class ManagedDispatcher +{ +public: + ManagedDispatcher(AsyncDispatcher* asyncDispatcher) + : asyncDispatcher(asyncDispatcher) + { + auto threadStart = gcnew Threading::ThreadStart(this, &ManagedDispatcher::threadMethod); + auto thread = gcnew Threading::Thread(threadStart); + thread->Start(); + } + +protected: + void threadMethod(); + AsyncDispatcher* asyncDispatcher; +}; + +/** + * Implementation of tsm::IAsyncDispatcher + */ +class AsyncDispatcher : public tsm::IAsyncDispatcher +{ +public: + virtual HANDLE dispatch(Method method, LPVOID lpParam) override { + this->method = method; + this->lpParam = lpParam; + gcnew ManagedDispatcher(this); + + exitThreadEvent.Attach(CreateEvent(nullptr, TRUE, FALSE, nullptr)); + return exitThreadEvent; + } + + /** + * Method called in the managed thread and call IAsyncDispatcher::Method. + */ + void threadMethod() { + method(lpParam); + SetEvent(exitThreadEvent); + } + +protected: + Method method; + LPVOID lpParam; + CHandle exitThreadEvent; +}; + +void ManagedDispatcher::threadMethod() +{ + asyncDispatcher->threadMethod(); +} + +tsm::IAsyncDispatcher* Context::_createAsyncDispatcher() +{ + return m_managedContext->useNativeThread ? new AsyncDispatcher() : nullptr; } Context::Context(ManagedType^ context, bool isAsync /*= true*/) @@ -67,15 +116,9 @@ Context::Context(ManagedType^ context, bool isAsync /*= true*/) { } -State::State(ManagedType^ state, ManagedType^ masterState, - ManagedType::HandleEventCallback handleEventCallback, - ManagedType::EntryCallback entryCallback, - ManagedType::ExitCallback exitCallback) +State::State(ManagedType^ state, ManagedType^ masterState) : m_managedState(state) , m_masterState(getNative(masterState)) - , m_handleEventCallback(handleEventCallback) - , m_entryCallback(entryCallback) - , m_exitCallback(exitCallback) { } @@ -86,18 +129,22 @@ State::~State() HRESULT State::_handleEvent(tsm::IContext* context, tsm::IEvent* event, tsm::IState** nextState) { - return m_handleEventCallback(context, event, nextState); + ManagedType^ _nextState = nullptr; + auto hr = m_managedState->handleEvent(getManaged((native::Context*)context), getManaged((native::Event*)event), _nextState); + if(_nextState) { + *nextState = _nextState->get(); + } + return (HRESULT)hr; } HRESULT State::_entry(tsm::IContext* context, tsm::IEvent* event, tsm::IState* previousState) { - return m_entryCallback(context, event, previousState); + return (HRESULT)m_managedState->entry(getManaged((native::Context*)context), getManaged((native::Event*)event), getManaged((native::State*)previousState)); } HRESULT State::_exit(tsm::IContext* context, tsm::IEvent* event, tsm::IState* nextState) { - Trace::WriteLine(String::Format("native::State::_exit(): Current AppDomain={0}", System::AppDomain::CurrentDomain->FriendlyName)); - return m_exitCallback(context, event, nextState); + return (HRESULT)m_managedState->exit(getManaged((native::Context*)context), getManaged((native::Event*)event), getManaged((native::State*)nextState)); } bool State::_isExitCalledOnShutdown() const @@ -105,12 +152,8 @@ bool State::_isExitCalledOnShutdown() const return m_managedState->IsExitCalledOnShutdown; } -Event::Event(ManagedType^ event, - ManagedType::PreHandleCallback preHandleCallback, - ManagedType::PostHandleCallback postHandleCallback) +Event::Event(ManagedType^ event) : m_managedEvent(event) - , m_preHandleCallback(preHandleCallback) - , m_postHandleCallback(postHandleCallback) , m_timerClient(nullptr) { } @@ -122,10 +165,10 @@ Event::~Event() HRESULT Event::_preHandle(tsm::IContext* context) { - return m_preHandleCallback(context); + return (HRESULT)m_managedEvent->preHandle(getManaged((native::Context*)context)); } HRESULT Event::_postHandle(tsm::IContext* context, HRESULT hr) { - return m_postHandleCallback(context, hr); + return (HRESULT)m_managedEvent->postHandle(getManaged((native::Context*)context), (tsm_NET::HResult)hr); } diff --git a/StateMachine.NET/NativeObjects.h b/StateMachine.NET/NativeObjects.h index c086351..899e8ce 100644 --- a/StateMachine.NET/NativeObjects.h +++ b/StateMachine.NET/NativeObjects.h @@ -13,13 +13,7 @@ class StateMonitor : public tsm::IStateMonitor public: using OwnerType = tsm_NET::StateMonitorCaller; - StateMonitor(OwnerType^ owner, - OwnerType::OnIdleCallback onIdleCallback, - OwnerType::OnEventTriggeredCallback onEventTriggeredCallback, - OwnerType::OnEventHandlingCallback onEventHandlingCallback, - OwnerType::OnStateChangedCallback onStateChangedCallback, - OwnerType::OnTimerStartedCallback onTimerStartedCallback, - OwnerType::OnWorkerThreadExitCallback onWorkerThreadExitCallback); + StateMonitor(OwnerType^ owner); virtual void onIdle(tsm::IContext* context) override; virtual void onEventTriggered(tsm::IContext* context, tsm::IEvent* event) override; @@ -34,12 +28,7 @@ class StateMonitor : public tsm::IStateMonitor virtual void onWorkerThreadExit(tsm::IContext* context, HRESULT exitCode) override; protected: - OwnerType::OnIdleCallback m_onIdleCallback; - OwnerType::OnEventTriggeredCallback m_onEventTriggeredCallback; - OwnerType::OnEventHandlingCallback m_onEventHandlingCallback; - OwnerType::OnStateChangedCallback m_onStateChangedCallback; - OwnerType::OnTimerStartedCallback m_onTimerStartedCallback; - OwnerType::OnWorkerThreadExitCallback m_onWorkerThreadExitCallback; + gcroot m_owner; }; class Context : public tsm::IContext, public tsm::TimerClient @@ -49,6 +38,7 @@ class Context : public tsm::IContext, public tsm::TimerClient Context(ManagedType^ context, bool isAsync = true); virtual bool isAsync() const override { return m_isAsync; } + virtual tsm::IAsyncDispatcher* _createAsyncDispatcher() override; HRESULT setup(tsm::IState* initialState, tsm::IEvent* event = nullptr) { return _getStateMachine()->setup(this, initialState, event); } HRESULT shutdown(DWORD timeout = 100) { return _getStateMachine()->shutdown(this, timeout); } @@ -88,10 +78,7 @@ class State : public tsm::IState, public tsm::TimerClient public: using ManagedType = tsm_NET::State; - State(ManagedType^ state, ManagedType^ masterState, - ManagedType::HandleEventCallback handleEventCallback, - ManagedType::EntryCallback entryCallback, - ManagedType::ExitCallback exitCallback); + State(ManagedType^ state, ManagedType^ masterState); virtual ~State(); #pragma region Implementation of IState that call methods of managed class. @@ -114,10 +101,6 @@ class State : public tsm::IState, public tsm::TimerClient protected: gcroot m_managedState; CComPtr m_masterState; - - ManagedType::HandleEventCallback m_handleEventCallback; - ManagedType::EntryCallback m_entryCallback; - ManagedType::ExitCallback m_exitCallback; }; class Event : public tsm::IEvent @@ -125,9 +108,7 @@ class Event : public tsm::IEvent public: using ManagedType = tsm_NET::Event; - Event(ManagedType^ event, - ManagedType::PreHandleCallback preHandleCallback, - ManagedType::PostHandleCallback postHandleCallback); + Event(ManagedType^ event); virtual ~Event(); #pragma region Implementation of IState that call methods of managed class. @@ -145,9 +126,6 @@ class Event : public tsm::IEvent protected: gcroot m_managedEvent; - ManagedType::PreHandleCallback m_preHandleCallback; - ManagedType::PostHandleCallback m_postHandleCallback; - int m_priority; DWORD m_delayTime; DWORD m_intervalTime; diff --git a/StateMachine.NET/StateMachine.NET.cpp b/StateMachine.NET/StateMachine.NET.cpp index 8a8bae2..50fd8e5 100644 --- a/StateMachine.NET/StateMachine.NET.cpp +++ b/StateMachine.NET/StateMachine.NET.cpp @@ -2,7 +2,6 @@ #include "StateMachine.NET.h" #include "NativeObjects.h" -#include "NativeCallback.h" using namespace tsm_NET; using namespace tsm_NET::common; @@ -10,24 +9,11 @@ using namespace System::Diagnostics; ///*static*/ event EventHandler^>^ IStateMonitor::AssertFailedEvent; -#define SAFE_RELEASE(ptr) \ - if(ptr) { \ - delete ptr; \ - ptr = nullptr; \ - } - //-------------- Implementation of IStateMonitorCaller. --------------------// StateMonitorCaller::StateMonitorCaller(IStateMonitor^ stateMonitor) : m_stateMonitor(stateMonitor) { - m_onIdleCallback = new native::Callback(gcnew OnIdleDelegate(this, &StateMonitorCaller::onIdleCallback)); - m_onEventTriggeredCallback = new native::Callback(gcnew OnEventTriggeredDelegate(this, &StateMonitorCaller::onEventTriggeredCallback)); - m_onEventHandlingCallback = new native::Callback(gcnew OnEventHandlingDelegate(this, &StateMonitorCaller::onEventHandlingCallback)); - m_onStateChangedCallback = new native::Callback(gcnew OnStateChangedDelegate(this, &StateMonitorCaller::onStateChangedCallback)); - m_onTimerStartedCallback = new native::Callback(gcnew OnTimerStartedDelegate(this, &StateMonitorCaller::onTimerStartedCallback)); - m_onWorkerThreadExitCallback = new native::Callback(gcnew OnWorkerThreadExitDelegate(this, &StateMonitorCaller::onWorkerThreadExitCallback)); - - m_nativeStateMonitor = new native::StateMonitor(this, *m_onIdleCallback, *m_onEventTriggeredCallback, * m_onEventHandlingCallback, *m_onStateChangedCallback, *m_onTimerStartedCallback, *m_onWorkerThreadExitCallback); + m_nativeStateMonitor = new native::StateMonitor(this); } StateMonitorCaller::~StateMonitorCaller() @@ -37,14 +23,10 @@ StateMonitorCaller::~StateMonitorCaller() StateMonitorCaller::!StateMonitorCaller() { - SAFE_RELEASE(m_nativeStateMonitor); - - SAFE_RELEASE(m_onIdleCallback); - SAFE_RELEASE(m_onEventTriggeredCallback); - SAFE_RELEASE(m_onEventHandlingCallback); - SAFE_RELEASE(m_onStateChangedCallback); - SAFE_RELEASE(m_onTimerStartedCallback); - SAFE_RELEASE(m_onWorkerThreadExitCallback); + if(m_nativeStateMonitor) { + delete m_nativeStateMonitor; + m_nativeStateMonitor = nullptr; + } } void StateMonitorCaller::onIdleCallback(tsm::IContext* context) @@ -78,8 +60,9 @@ void StateMonitorCaller::onWorkerThreadExitCallback(tsm::IContext* context, HRES } //-------------- Managed Context class. --------------------// -void Context::construct(bool isAsync) +void Context::construct(bool isAsync, bool useNativeThread) { + m_useNativeThread = useNativeThread; m_nativeContext = new native::Context(this, isAsync); } @@ -147,58 +130,11 @@ void Context::StateMonitor::set(IStateMonitor^ value) //-------------- Managed State class. --------------------// State::State(State^ masterState) { - m_handleEventCallback = new native::Callback(gcnew HandleEventDelegate(this, &State::handleEventCallback)); - m_entryCallback = new native::Callback (gcnew EntryDelegate(this, &State::entryCallback)); - m_exitCallback = new native::Callback (gcnew ExitDelegate(this, &State::exitCallback)); - - m_nativeState = new native::State(this, masterState, *m_handleEventCallback, *m_entryCallback, *m_exitCallback); - //m_nativeState->AddRef(); + m_nativeState = new native::State(this, masterState); IsExitCalledOnShutdown = false; } -State::~State() -{ - this->!State(); -} - -State::!State() -{ - if(m_nativeState) - { - auto c = 0;// m_nativeState->Release(); - Console::WriteLine("Released State {0}, ref={1}", (IntPtr)m_nativeState, c); - m_nativeState = nullptr; - } - - SAFE_RELEASE(m_handleEventCallback); - SAFE_RELEASE(m_entryCallback); - SAFE_RELEASE(m_exitCallback); -} - -HRESULT State::handleEventCallback(tsm::IContext* context, tsm::IEvent* event, tsm::IState** nextState) -{ - State^ _nextState = nullptr; - auto hr = handleEvent(getManaged((native::Context*)context), getManaged((native::Event*)event), _nextState); - if(_nextState) { - *nextState = getNative(_nextState); - } - return (HRESULT)hr; -} - -HRESULT State::entryCallback(tsm::IContext* context, tsm::IEvent* event, tsm::IState* previousState) -{ - return (HRESULT)entry(getManaged((native::Context*)context), getManaged((native::Event*)event), getManaged((native::State*)previousState)); -} - -HRESULT State::exitCallback(tsm::IContext* context, tsm::IEvent* event, tsm::IState* nextState) -{ - Trace::WriteLine(String::Format("State::exitCallback(): Current AppDomain={0}", System::AppDomain::CurrentDomain->FriendlyName)); - auto ret = exit(getManaged((native::Context*)context), getManaged((native::Event*)event), getManaged((native::State*)nextState)); - //m_nativeState->Release(); - return (HRESULT)ret; -} - State^ State::getMasterState() { return getManaged(m_nativeState->getMasterState()); @@ -233,43 +169,10 @@ void State::MemoryWeight::set(int value) //-------------- Managed Event class. --------------------// Event::Event() { - m_preHandleCallback = new native::Callback(gcnew PreHandleDelegate(this, &Event::preHandleCallback)); - m_postHandleCallback = new native::Callback(gcnew PostHandleDelegate(this, &Event::postHandleCallback)); - - m_nativeEvent = new native::Event(this, *m_preHandleCallback, *m_postHandleCallback); + m_nativeEvent = new native::Event(this); //m_nativeEvent->AddRef(); } -Event::~Event() -{ - this->!Event(); -} - -Event::!Event() -{ - if(m_nativeEvent) - { - auto c = 0;// m_nativeEvent->Release(); - Console::WriteLine("Released Event {0}, ref={1}", (IntPtr)m_nativeEvent, c); - m_nativeEvent = nullptr; - } - - SAFE_RELEASE(m_preHandleCallback); - SAFE_RELEASE(m_postHandleCallback); -} - -HRESULT Event::preHandleCallback(tsm::IContext* context) -{ - return (HRESULT)preHandle(getManaged((native::Context*)context)); -} - -HRESULT Event::postHandleCallback(tsm::IContext* context, HRESULT hr) -{ - auto ret = (HRESULT)postHandle(getManaged((native::Context*)context), (HResult)hr); - //m_nativeEvent->Release(); - return (HRESULT)ret; -} - long Event::SequenceNumber::get() { return m_nativeEvent->getSequenceNumber(); diff --git a/StateMachine.NET/StateMachine.NET.h b/StateMachine.NET/StateMachine.NET.h index 683bb92..7ce4859 100644 --- a/StateMachine.NET/StateMachine.NET.h +++ b/StateMachine.NET/StateMachine.NET.h @@ -8,9 +8,6 @@ class Context; class State; class Event; class StateMonitor; - -template -class Callback; } namespace tsm_NET @@ -64,28 +61,16 @@ public ref class StateMonitorCaller #pragma region Definition of delegate, callback signature and callback method. See native::Callback<> template class. // void IStateMonitor::onIdle() - delegate void OnIdleDelegate(tsm::IContext* context); - using OnIdleCallback = void(__stdcall *)(tsm::IContext* context); virtual void onIdleCallback(tsm::IContext* context); // void IstateMonitor::onEventTriggered() - delegate void OnEventTriggeredDelegate(tsm::IContext* context, tsm::IEvent* event); - using OnEventTriggeredCallback = void(__stdcall *)(tsm::IContext* context, tsm::IEvent* event); virtual void onEventTriggeredCallback(tsm::IContext* context, tsm::IEvent* event); // void IStateMonitor::onEventHandling() - delegate void OnEventHandlingDelegate(tsm::IContext* context, tsm::IEvent* event, tsm::IState* current); - using OnEventHandlingCallback = void(__stdcall *)(tsm::IContext* context, tsm::IEvent* event, tsm::IState* current); virtual void onEventHandlingCallback(tsm::IContext* context, tsm::IEvent* event, tsm::IState* current); // void IStateMonitor::onStateChanged() - delegate void OnStateChangedDelegate(tsm::IContext* context, tsm::IEvent* event, tsm::IState* previous, tsm::IState* next); - using OnStateChangedCallback = void(__stdcall *)(tsm::IContext* context, tsm::IEvent* event, tsm::IState* previous, tsm::IState* next); virtual void onStateChangedCallback(tsm::IContext* context, tsm::IEvent* event, tsm::IState* previous, tsm::IState* next); // void IStateMonitor::onTimerStarted() - delegate void OnTimerStartedDelegate(tsm::IContext* context, tsm::IEvent* event); - using OnTimerStartedCallback = void(__stdcall *)(tsm::IContext* context, tsm::IEvent* event); virtual void onTimerStartedCallback(tsm::IContext* context, tsm::IEvent* event); // void IStateMonitor::onWorkerThreadExit() - delegate void OnWorkerThreadExitDelegate(tsm::IContext* context, HRESULT exitCode); - using OnWorkerThreadExitCallback = void(__stdcall *)(tsm::IContext* context, HRESULT exitCode); virtual void onWorkerThreadExitCallback(tsm::IContext* context, HRESULT exitCode); #pragma endregion @@ -94,25 +79,17 @@ public ref class StateMonitorCaller protected: NativeType* m_nativeStateMonitor; IStateMonitor^ m_stateMonitor; - -internal: - native::Callback* m_onIdleCallback; - native::Callback* m_onEventTriggeredCallback; - native::Callback* m_onEventHandlingCallback; - native::Callback* m_onStateChangedCallback; - native::Callback* m_onTimerStartedCallback; - native::Callback* m_onWorkerThreadExitCallback; }; public ref class Context { - void construct(bool isAsync); + void construct(bool isAsync, bool useNativeThread); -public: - using NativeType = native::Context; +protected: + Context(bool isAsync, bool useNativeThread) { construct(isAsync, useNativeThread); } - Context() { construct(true); } - Context(bool isAsync) { construct(isAsync); } +public: + Context() { construct(false, false); } virtual ~Context(); !Context(); @@ -137,23 +114,32 @@ public ref class Context #pragma endregion internal: + using NativeType = native::Context; + NativeType* get() { return m_nativeContext; } + property bool useNativeThread { bool get() { return m_useNativeThread; } } protected: NativeType* m_nativeContext; - tsm_NET::IStateMonitor^ m_stateMonitor; + bool m_useNativeThread; StateMonitorCaller^ m_stateMonitorCaller; + tsm_NET::IStateMonitor^ m_stateMonitor; }; -public ref class State +public ref class AsyncContext : public Context { public: - using NativeType = native::State; + AsyncContext() : Context(true, false) {} + AsyncContext(bool useNativeThread) : Context(true, useNativeThread) {} +protected: +}; + +public ref class State +{ +public: State() : State(nullptr) {} State(State^ masterState); - virtual ~State(); - !State(); #pragma region Methods to be implemented by sub class. virtual HResult handleEvent(Context^ context, Event^ event, State^% nextState) { return HResult::Ok; } @@ -175,39 +161,18 @@ public ref class State #pragma endregion internal: - NativeType* get() { return m_nativeState; } - -#pragma region Definition of delegate, callback signature and callback method. See native::Callback<> template class. - delegate HRESULT HandleEventDelegate(tsm::IContext* context, tsm::IEvent* event, tsm::IState** nextState); - using HandleEventCallback = HRESULT (__stdcall *)(tsm::IContext* context, tsm::IEvent* event, tsm::IState** nextState); - virtual HRESULT handleEventCallback(tsm::IContext* context, tsm::IEvent* event, tsm::IState** nextState); - - delegate HRESULT EntryDelegate(tsm::IContext* context, tsm::IEvent* event, tsm::IState* previousState); - using EntryCallback = HRESULT (__stdcall *)(tsm::IContext* context, tsm::IEvent* event, tsm::IState* previousState); - virtual HRESULT entryCallback(tsm::IContext* context, tsm::IEvent* event, tsm::IState* previousState); + using NativeType = native::State; - delegate HRESULT ExitDelegate(tsm::IContext* context, tsm::IEvent* event, tsm::IState* nextState); - using ExitCallback = HRESULT(__stdcall *)(tsm::IContext* context, tsm::IEvent* event, tsm::IState* nextState); - virtual HRESULT exitCallback(tsm::IContext* context, tsm::IEvent* event, tsm::IState* nextState); -#pragma endregion + NativeType* get() { return m_nativeState; } protected: NativeType* m_nativeState; - -internal: - native::Callback* m_handleEventCallback; - native::Callback* m_entryCallback; - native::Callback* m_exitCallback; }; public ref class Event { public: - using NativeType = native::Event; - Event(); - virtual ~Event(); - !Event(); #pragma region Methods to be implemented by sub class. virtual HResult preHandle(Context^ context) { return HResult::Ok; } @@ -224,24 +189,12 @@ public ref class Event #pragma endregion internal: - NativeType* get() { return m_nativeEvent; } - -#pragma region Definition of delegate, callback signature and callback method. See tsm::ICallback<> template class. - delegate HRESULT PreHandleDelegate(tsm::IContext* context); - using PreHandleCallback = HRESULT(__stdcall *)(tsm::IContext* context); - virtual HRESULT preHandleCallback(tsm::IContext* context); + using NativeType = native::Event; - delegate HRESULT PostHandleDelegate(tsm::IContext* context, HRESULT hr); - using PostHandleCallback = HRESULT(__stdcall *)(tsm::IContext* context, HRESULT hr); - virtual HRESULT postHandleCallback(tsm::IContext* context, HRESULT hr); -#pragma endregion + NativeType* get() { return m_nativeEvent; } protected: NativeType* m_nativeEvent; - -internal: - native::Callback* m_preHandleCallback; - native::Callback* m_postHandleCallback; }; namespace common diff --git a/StateMachine.NET/StateMachine.NET.vcxproj b/StateMachine.NET/StateMachine.NET.vcxproj index a5994c1..4ad9495 100644 --- a/StateMachine.NET/StateMachine.NET.vcxproj +++ b/StateMachine.NET/StateMachine.NET.vcxproj @@ -138,7 +138,6 @@ - diff --git a/StateMachine/AsyncStateMachine.cpp b/StateMachine/AsyncStateMachine.cpp index f91c278..db3e4ee 100644 --- a/StateMachine/AsyncStateMachine.cpp +++ b/StateMachine/AsyncStateMachine.cpp @@ -16,6 +16,15 @@ namespace tsm { return context->isAsync() ? new AsyncStateMachine() : new StateMachine(); } +class DefaultAsyncDispatcher : public IAsyncDispatcher +{ +public: + virtual HANDLE dispatch(Method method, LPVOID param) override + { + return CreateThread(nullptr, 0, method, param, 0, nullptr); + } +}; + HRESULT AsyncStateMachine::setup(IContext * context, IState * initialState, IEvent * event) { // Ensure to release object on error. @@ -38,7 +47,11 @@ HRESULT AsyncStateMachine::setup(IContext * context, IState * initialState, IEve asyncData->hEventShutdown.Attach(CreateEvent(NULL, TRUE, FALSE, NULL)); // param will be deleted in worker thread. auto param = new SetupParam(context, this, event); - asyncData->hWorkerThread.Attach(CreateThread(nullptr, 0, workerThreadProc, param, 0, nullptr)); + + auto dispatcher = context->_createAsyncDispatcher(); + if(!dispatcher) { dispatcher = new DefaultAsyncDispatcher(); } + asyncData->asyncDispatcher.reset(dispatcher); + asyncData->hWorkerThread.Attach(dispatcher->dispatch(workerThreadProc, param)); if(!asyncData->hWorkerThread) { delete param; return HRESULT_FROM_WIN32(GetLastError()); diff --git a/StateMachine/Handles.h b/StateMachine/Handles.h index 254a509..fa450e1 100644 --- a/StateMachine/Handles.h +++ b/StateMachine/Handles.h @@ -32,6 +32,7 @@ struct ContextHandle // Data for IAsyncContext used to perform async operation. struct AsyncData { + std::unique_ptr asyncDispatcher; std::deque> eventQueue; // FIFO of IEvent to be handled. std::recursive_mutex eventQueueLock; // Lock to modify IEvent queue. CHandle hEventReady; // Event handle set when ready to handle IEvent(entry() of Initial state completes). diff --git a/public/include/StateMachine/Context.h b/public/include/StateMachine/Context.h index 2256ff7..0a58eb5 100644 --- a/public/include/StateMachine/Context.h +++ b/public/include/StateMachine/Context.h @@ -13,6 +13,7 @@ class Context : public IContext, public TimerClient virtual ~Context() {} virtual bool isAsync() const { return false; } + virtual IAsyncDispatcher* _createAsyncDispatcher() override { return nullptr; } HRESULT setup(S* initialState, E* event = nullptr) { return _getStateMachine()->setup(this, initialState, event); } HRESULT shutdown(DWORD timeout = 100) { return _getStateMachine()->shutdown(this, timeout); } diff --git a/public/include/StateMachine/Interface.h b/public/include/StateMachine/Interface.h index 144ffd0..2543b69 100644 --- a/public/include/StateMachine/Interface.h +++ b/public/include/StateMachine/Interface.h @@ -38,12 +38,34 @@ class HandleOwner std::unique_ptr m_handle; }; +/** + * IAsyncDispatcher interface + */ +class IAsyncDispatcher +{ +public: + /** + * Signature of method to be dispatched. + */ + using Method = DWORD(WINAPI *)(LPVOID lpParam); + + /** + * dispathc method. + * + * method: Method to be dispatched. + * lpParam: Pointer to parameter to be passed to `method`. + * Returns event handle that is set when the method terminates. + */ + virtual HANDLE dispatch(Method method, LPVOID lpParam) = 0; +}; + class IContext : public HandleOwner { public: virtual ~IContext() {} virtual bool isAsync() const = 0; + virtual IAsyncDispatcher* _createAsyncDispatcher() = 0; virtual IStateMachine* _getStateMachine() = 0; virtual IState* _getCurrentState() = 0;