Mercurial > hg > orthanc-stone
changeset 299:3897f9f28cfa am-callable-and-promise
backup work in progress: updated messaging framework with ICallable
line wrap: on
line diff
--- a/Applications/Samples/CMakeLists.txt Mon Sep 10 12:22:26 2018 +0200 +++ b/Applications/Samples/CMakeLists.txt Fri Sep 14 16:44:01 2018 +0200 @@ -140,6 +140,7 @@ ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestCommands.cpp ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestExceptions.cpp ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestMessageBroker.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestMessageBroker2.cpp ${ORTHANC_STONE_ROOT}/UnitTestsSources/UnitTestsMain.cpp )
--- a/Applications/Samples/SimpleViewerApplication.h Mon Sep 10 12:22:26 2018 +0200 +++ b/Applications/Samples/SimpleViewerApplication.h Fri Sep 14 16:44:01 2018 +0200 @@ -204,12 +204,7 @@ wasmViewport1_(NULL), wasmViewport2_(NULL) { - DeclareIgnoredMessage(MessageType_Widget_ContentChanged); - DeclareHandledMessage(MessageType_Widget_GeometryChanged); - - DeclareHandledMessage(MessageType_OrthancApi_GetStudyIds_Ready); - DeclareHandledMessage(MessageType_OrthancApi_GetStudy_Ready); - DeclareHandledMessage(MessageType_OrthancApi_GetSeries_Ready); +// DeclareIgnoredMessage(MessageType_Widget_ContentChanged); } virtual void Finalize() {} @@ -249,7 +244,7 @@ thumbnailsLayout_->SetVertical(); mainWidget_ = new LayerWidget(broker_, "main-viewport"); - mainWidget_->RegisterObserver(*this); + //mainWidget_->RegisterObserver(*this); // hierarchy mainLayout_->AddWidget(thumbnailsLayout_); @@ -273,7 +268,7 @@ if (parameters.count("studyId") < 1) { LOG(WARNING) << "The study ID is missing, will take the first studyId found in Orthanc"; - orthancApiClient_->ScheduleGetStudyIds(*this); + orthancApiClient_->GetJsonAsync("/studies", new Callable<SimpleViewerApplication, OrthancApiClient::NewGetJsonResponseReadyMessage>(*this, &SimpleViewerApplication::OnStudyListReceived)); } else { @@ -281,26 +276,32 @@ } } - void OnStudyListReceived(const Json::Value& response) + void OnStudyListReceived(const OrthancApiClient::NewGetJsonResponseReadyMessage& message) { + const Json::Value& response = message.response_; + if (response.isArray() && response.size() > 1) { SelectStudy(response[0].asString()); } } - void OnStudyReceived(const Json::Value& response) + void OnStudyReceived(const OrthancApiClient::NewGetJsonResponseReadyMessage& message) { + const Json::Value& response = message.response_; + if (response.isObject() && response["Series"].isArray()) { for (size_t i=0; i < response["Series"].size(); i++) { - orthancApiClient_->ScheduleGetSeries(*this, response["Series"][(int)i].asString()); + orthancApiClient_->GetJsonAsync("/series/" + response["Series"][(int)i].asString(), new Callable<SimpleViewerApplication, OrthancApiClient::NewGetJsonResponseReadyMessage>(*this, &SimpleViewerApplication::OnSeriesReceived)); } } } - void OnSeriesReceived(const Json::Value& response) + void OnSeriesReceived(const OrthancApiClient::NewGetJsonResponseReadyMessage& message) { + const Json::Value& response = message.response_; + if (response.isObject() && response["Instances"].isArray() && response["Instances"].size() > 0) { // keep track of all instances IDs @@ -330,34 +331,19 @@ LayerWidget* thumbnailWidget = new LayerWidget(broker_, "thumbnail-series-" + seriesId); thumbnails_.push_back(thumbnailWidget); thumbnailsLayout_->AddWidget(thumbnailWidget); - thumbnailWidget->RegisterObserver(*this); + thumbnailWidget->RegisterObserverCallback(new Callable<SimpleViewerApplication, LayerWidget::GeometryChangedMessage>(*this, &SimpleViewerApplication::OnWidgetGeometryChanged)); thumbnailWidget->AddLayer(smartLoader_->GetFrame(instanceId, 0)); thumbnailWidget->SetInteractor(*thumbnailInteractor_); } void SelectStudy(const std::string& studyId) { - orthancApiClient_->ScheduleGetStudy(*this, studyId); + orthancApiClient_->GetJsonAsync("/studies/" + studyId, new Callable<SimpleViewerApplication, OrthancApiClient::NewGetJsonResponseReadyMessage>(*this, &SimpleViewerApplication::OnStudyReceived)); } - virtual void HandleMessage(IObservable& from, const IMessage& message) { - switch (message.GetType()) { - case MessageType_Widget_GeometryChanged: - LOG(INFO) << "Widget geometry ready: " << dynamic_cast<LayerWidget&>(from).GetName(); - dynamic_cast<LayerWidget&>(from).SetDefaultView(); - break; - case MessageType_OrthancApi_GetStudyIds_Ready: - OnStudyListReceived(dynamic_cast<const OrthancApiClient::GetJsonResponseReadyMessage&>(message).response_); - break; - case MessageType_OrthancApi_GetSeries_Ready: - OnSeriesReceived(dynamic_cast<const OrthancApiClient::GetJsonResponseReadyMessage&>(message).response_); - break; - case MessageType_OrthancApi_GetStudy_Ready: - OnStudyReceived(dynamic_cast<const OrthancApiClient::GetJsonResponseReadyMessage&>(message).response_); - break; - default: - VLOG("unhandled message type" << message.GetType()); - } + void OnWidgetGeometryChanged(const LayerWidget::GeometryChangedMessage& message) + { + message.origin_.SetDefaultView(); } void SelectSeriesInMainViewport(const std::string& seriesId)
--- a/Framework/Layers/ILayerSource.h Mon Sep 10 12:22:26 2018 +0200 +++ b/Framework/Layers/ILayerSource.h Fri Sep 14 16:44:01 2018 +0200 @@ -31,6 +31,11 @@ class ILayerSource : public IObservable { public: + + typedef NoPayloadMessage<MessageType_LayerSource_GeometryReady> GeometryReadyMessage; + typedef NoPayloadMessage<MessageType_LayerSource_GeometryError> GeometryErrorMessage; + typedef NoPayloadMessage<MessageType_LayerSource_ContentChanged> ContentChangedMessage; + struct SliceChangedMessage : public IMessage { const Slice& slice_;
--- a/Framework/Layers/LayerSourceBase.cpp Mon Sep 10 12:22:26 2018 +0200 +++ b/Framework/Layers/LayerSourceBase.cpp Fri Sep 14 16:44:01 2018 +0200 @@ -27,17 +27,17 @@ { void LayerSourceBase::NotifyGeometryReady() { - EmitMessage(IMessage(MessageType_LayerSource_GeometryReady)); + EmitMessage(ILayerSource::GeometryReadyMessage()); } void LayerSourceBase::NotifyGeometryError() { - EmitMessage(IMessage(MessageType_LayerSource_GeometryError)); + EmitMessage(ILayerSource::GeometryErrorMessage()); } void LayerSourceBase::NotifyContentChange() { - EmitMessage(IMessage(MessageType_LayerSource_ContentChanged)); + EmitMessage(ILayerSource::ContentChangedMessage()); } void LayerSourceBase::NotifySliceChange(const Slice& slice)
--- a/Framework/Layers/LayerSourceBase.h Mon Sep 10 12:22:26 2018 +0200 +++ b/Framework/Layers/LayerSourceBase.h Fri Sep 14 16:44:01 2018 +0200 @@ -44,11 +44,11 @@ LayerSourceBase(MessageBroker& broker) : ILayerSource(broker) { - DeclareEmittableMessage(MessageType_LayerSource_GeometryReady); - DeclareEmittableMessage(MessageType_LayerSource_GeometryError); - DeclareEmittableMessage(MessageType_LayerSource_ContentChanged); - DeclareEmittableMessage(MessageType_LayerSource_SliceChanged); - DeclareEmittableMessage(MessageType_LayerSource_LayerReady); +// DeclareEmittableMessage(MessageType_LayerSource_GeometryReady); +// DeclareEmittableMessage(MessageType_LayerSource_GeometryError); +// DeclareEmittableMessage(MessageType_LayerSource_ContentChanged); +// DeclareEmittableMessage(MessageType_LayerSource_SliceChanged); +// DeclareEmittableMessage(MessageType_LayerSource_LayerReady); } };
--- a/Framework/Layers/OrthancFrameLayerSource.cpp Mon Sep 10 12:22:26 2018 +0200 +++ b/Framework/Layers/OrthancFrameLayerSource.cpp Fri Sep 14 16:44:01 2018 +0200 @@ -31,46 +31,35 @@ namespace OrthancStone { - void OrthancFrameLayerSource::HandleMessage(IObservable& from, const IMessage& message) + + void OrthancFrameLayerSource::OnSliceGeometryReady(const OrthancSlicesLoader::SliceGeometryReadyMessage& message) { - switch (message.GetType()) - { - case MessageType_SliceLoader_GeometryReady: - { - const OrthancSlicesLoader& loader = dynamic_cast<const OrthancSlicesLoader&>(from); - if (loader.GetSliceCount() > 0) - { - LayerSourceBase::NotifyGeometryReady(); - } - else - { - LayerSourceBase::NotifyGeometryError(); - } - - }; break; - case MessageType_SliceLoader_GeometryError: + if (message.origin_.GetSliceCount() > 0) { - const OrthancSlicesLoader& loader = dynamic_cast<const OrthancSlicesLoader&>(from); - LayerSourceBase::NotifyGeometryError(); - }; break; - case MessageType_SliceLoader_ImageReady: + LayerSourceBase::NotifyGeometryReady(); + } + else { - const OrthancSlicesLoader::SliceImageReadyMessage& msg = dynamic_cast<const OrthancSlicesLoader::SliceImageReadyMessage&>(message); - bool isFull = (msg.effectiveQuality_ == SliceImageQuality_FullPng || msg.effectiveQuality_ == SliceImageQuality_FullPam); - LayerSourceBase::NotifyLayerReady(FrameRenderer::CreateRenderer(msg.image_.release(), msg.slice_, isFull), - msg.slice_.GetGeometry(), false); - - }; break; - case MessageType_SliceLoader_ImageError: - { - const OrthancSlicesLoader::SliceImageErrorMessage& msg = dynamic_cast<const OrthancSlicesLoader::SliceImageErrorMessage&>(message); - LayerSourceBase::NotifyLayerReady(NULL, msg.slice_.GetGeometry(), true); - }; break; - default: - VLOG("unhandled message type" << message.GetType()); + LayerSourceBase::NotifyGeometryError(); } } + void OrthancFrameLayerSource::OnSliceGeometryError(const OrthancSlicesLoader::SliceGeometryErrorMessage& message) + { + LayerSourceBase::NotifyGeometryError(); + } + + void OrthancFrameLayerSource::OnSliceImageReady(const OrthancSlicesLoader::SliceImageReadyMessage& message) + { + bool isFull = (message.effectiveQuality_ == SliceImageQuality_FullPng || message.effectiveQuality_ == SliceImageQuality_FullPam); + LayerSourceBase::NotifyLayerReady(FrameRenderer::CreateRenderer(message.image_.release(), message.slice_, isFull), + message.slice_.GetGeometry(), false); + } + + void OrthancFrameLayerSource::OnSliceImageError(const OrthancSlicesLoader::SliceImageErrorMessage& message) + { + LayerSourceBase::NotifyLayerReady(NULL, message.slice_.GetGeometry(), true); + } OrthancFrameLayerSource::OrthancFrameLayerSource(MessageBroker& broker, IWebService& orthanc) : LayerSourceBase(broker), @@ -79,11 +68,14 @@ loader_(broker, orthanc), quality_(SliceImageQuality_FullPng) { - DeclareHandledMessage(MessageType_SliceLoader_GeometryReady); - DeclareHandledMessage(MessageType_SliceLoader_GeometryError); - DeclareHandledMessage(MessageType_SliceLoader_ImageReady); - DeclareHandledMessage(MessageType_SliceLoader_ImageError); - loader_.RegisterObserver(*this); +// DeclareHandledMessage(MessageType_SliceLoader_GeometryReady); +// DeclareHandledMessage(MessageType_SliceLoader_GeometryError); +// DeclareHandledMessage(MessageType_SliceLoader_ImageReady); +// DeclareHandledMessage(MessageType_SliceLoader_ImageError); + loader_.RegisterObserverCallback(new Callable<OrthancFrameLayerSource, OrthancSlicesLoader::SliceGeometryReadyMessage>(*this, &OrthancFrameLayerSource::OnSliceGeometryReady)); + loader_.RegisterObserverCallback(new Callable<OrthancFrameLayerSource, OrthancSlicesLoader::SliceGeometryErrorMessage>(*this, &OrthancFrameLayerSource::OnSliceGeometryError)); + loader_.RegisterObserverCallback(new Callable<OrthancFrameLayerSource, OrthancSlicesLoader::SliceImageReadyMessage>(*this, &OrthancFrameLayerSource::OnSliceImageReady)); + loader_.RegisterObserverCallback(new Callable<OrthancFrameLayerSource, OrthancSlicesLoader::SliceImageErrorMessage>(*this, &OrthancFrameLayerSource::OnSliceImageError)); }
--- a/Framework/Layers/OrthancFrameLayerSource.h Mon Sep 10 12:22:26 2018 +0200 +++ b/Framework/Layers/OrthancFrameLayerSource.h Fri Sep 14 16:44:01 2018 +0200 @@ -69,6 +69,11 @@ virtual void ScheduleLayerCreation(const CoordinateSystem3D& viewportSlice); - virtual void HandleMessage(IObservable& from, const IMessage& message); +protected: + void OnSliceGeometryReady(const OrthancSlicesLoader::SliceGeometryReadyMessage& message); + void OnSliceGeometryError(const OrthancSlicesLoader::SliceGeometryErrorMessage& message); + void OnSliceImageReady(const OrthancSlicesLoader::SliceImageReadyMessage& message); + void OnSliceImageError(const OrthancSlicesLoader::SliceImageErrorMessage& message); +// virtual void HandleMessage(IObservable& from, const IMessage& message); }; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Messages/ICallable.h Fri Sep 14 16:44:01 2018 +0200 @@ -0,0 +1,92 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "IMessage.h" + +#include <boost/noncopyable.hpp> + +namespace OrthancStone { + + class IObserver; + + // This is referencing an object and member function that can be notified + // by an IObservable. The object must derive from IO + // The member functions must be of type "void Function(const IMessage& message)" or reference a derived class of IMessage + class ICallable : public boost::noncopyable + { + public: + virtual ~ICallable() + { + } + + virtual void Apply(const IMessage& message) = 0; + + virtual MessageType GetMessageType() const = 0; + virtual IObserver* GetObserver() const = 0; + }; + + template <typename TMessage> + class MessageHandler: public ICallable + { + }; + + + template <typename TObserver, + typename TMessage> + class Callable : public MessageHandler<TMessage> + { + private: + typedef void (TObserver::* MemberFunction) (const TMessage&); + + TObserver& observer_; + MemberFunction function_; + + public: + Callable(TObserver& observer, + MemberFunction function) : + observer_(observer), + function_(function) + { + } + + void ApplyInternal(const TMessage& message) + { + (observer_.*function_) (message); + } + + virtual void Apply(const IMessage& message) + { + ApplyInternal(dynamic_cast<const TMessage&>(message)); + } + + virtual MessageType GetMessageType() const + { + return static_cast<MessageType>(TMessage::Type); + } + + virtual IObserver* GetObserver() const + { + return &observer_; + } + }; +}
--- a/Framework/Messages/IMessage.h Mon Sep 10 12:22:26 2018 +0200 +++ b/Framework/Messages/IMessage.h Fri Sep 14 16:44:01 2018 +0200 @@ -27,16 +27,61 @@ namespace OrthancStone { - struct IMessage : public boost::noncopyable + + // base message that are exchanged between IObservable and IObserver + struct IMessage : public boost::noncopyable { MessageType messageType_; - public: + protected: IMessage(const MessageType& messageType) : messageType_(messageType) {} + public: virtual ~IMessage() {} MessageType GetType() const {return messageType_;} }; + + // base class to derive from to implement your own messages + // it handles the message type for you + template <MessageType type> + struct BaseMessage : public IMessage + { + enum + { + Type = type + }; + + BaseMessage() + : IMessage(static_cast<MessageType>(Type)) + {} + }; + + // simple message implementation when no payload is needed + // sample usage: + // typedef NoPayloadMessage<MessageType_LayerSource_GeometryReady> GeometryReadyMessage; + template <MessageType type> + struct NoPayloadMessage : public BaseMessage<type> + { + NoPayloadMessage() + : BaseMessage<type>() + {} + + }; + + // simple message implementation when no payload is needed but the origin is required + // sample usage: + // typedef OriginMessage<MessageType_SliceLoader_GeometryError, OrthancSlicesLoader> SliceGeometryErrorMessage; + template <MessageType type, typename TOrigin> + struct OriginMessage : public BaseMessage<type> + { + TOrigin& origin_; + OriginMessage(TOrigin& origin) + : BaseMessage<type>(), + origin_(origin) + {} + + }; + }
--- a/Framework/Messages/IObservable.h Mon Sep 10 12:22:26 2018 +0200 +++ b/Framework/Messages/IObservable.h Fri Sep 14 16:44:01 2018 +0200 @@ -25,9 +25,11 @@ #include <assert.h> #include <algorithm> #include <iostream> +#include <map> #include "MessageBroker.h" #include "MessageType.h" +#include "ICallable.h" #include "IObserver.h" namespace OrthancStone { @@ -48,7 +50,8 @@ protected: MessageBroker& broker_; - std::set<IObserver*> observers_; + typedef std::map<int, std::set<ICallable*> > Callables; + Callables callables_; std::set<MessageType> emittableMessages_; public: @@ -59,6 +62,22 @@ } virtual ~IObservable() { + for (Callables::const_iterator it = callables_.begin(); + it != callables_.end(); ++it) + { + for (std::set<ICallable*>::const_iterator + it2 = it->second.begin(); it2 != it->second.end(); ++it2) + { + delete *it2; + } + } + } + + void RegisterObserverCallback(ICallable* callable) + { + MessageType messageType = callable->GetMessageType(); + + callables_[messageType].insert(callable); } void EmitMessage(const IMessage& message) @@ -68,25 +87,24 @@ throw MessageNotDeclaredException(message.GetType()); } - broker_.EmitMessage(*this, observers_, message); - } + Callables::const_iterator found = callables_.find(message.GetType()); - void RegisterObserver(IObserver& observer) - { - CheckObserverDeclaredAllObservableMessages(observer); - observers_.insert(&observer); + if (found != callables_.end()) + { + for (std::set<ICallable*>::const_iterator + it = found->second.begin(); it != found->second.end(); ++it) + { + if (broker_.IsActive((*it)->GetObserver())) + { + (*it)->Apply(message); + } + } + } } - - void UnregisterObserver(IObserver& observer) - { - observers_.erase(&observer); - } - const std::set<MessageType>& GetEmittableMessages() const { return emittableMessages_; } - protected: void DeclareEmittableMessage(MessageType messageType) @@ -94,18 +112,6 @@ emittableMessages_.insert(messageType); } - void CheckObserverDeclaredAllObservableMessages(IObserver& observer) - { - for (std::set<MessageType>::const_iterator it = emittableMessages_.begin(); it != emittableMessages_.end(); it++) - { - // the observer must have "declared" all observable messages - if (observer.GetHandledMessages().find(*it) == observer.GetHandledMessages().end() - && observer.GetIgnoredMessages().find(*it) == observer.GetIgnoredMessages().end()) - { - throw MessageNotDeclaredException(*it); - } - } - } }; }
--- a/Framework/Messages/IObserver.h Mon Sep 10 12:22:26 2018 +0200 +++ b/Framework/Messages/IObserver.h Fri Sep 14 16:44:01 2018 +0200 @@ -49,39 +49,39 @@ broker_.Unregister(*this); } - void HandleMessage_(IObservable &from, const IMessage &message) - { - assert(handledMessages_.find(message.GetType()) != handledMessages_.end()); // please declare the messages that you're handling +// void HandleMessage_(IObservable &from, const IMessage &message) +// { +// assert(handledMessages_.find(message.GetType()) != handledMessages_.end()); // please declare the messages that you're handling - HandleMessage(from, message); - } +// HandleMessage(from, message); +// } - virtual void HandleMessage(IObservable& from, const IMessage& message) = 0; +// virtual void HandleMessage(IObservable& from, const IMessage& message) = 0; - const std::set<MessageType>& GetHandledMessages() const - { - return handledMessages_; - } +// const std::set<MessageType>& GetHandledMessages() const +// { +// return handledMessages_; +// } - const std::set<MessageType>& GetIgnoredMessages() const - { - return ignoredMessages_; - } +// const std::set<MessageType>& GetIgnoredMessages() const +// { +// return ignoredMessages_; +// } - protected: +// protected: - // when you connect an IObserver to an IObservable, the observer must handle all observable messages (this is checked during the registration) - // so, all messages that may be emitted by the observable must be declared "handled" or "ignored" by the observer - void DeclareHandledMessage(MessageType messageType) - { - handledMessages_.insert(messageType); - } +// // when you connect an IObserver to an IObservable, the observer must handle all observable messages (this is checked during the registration) +// // so, all messages that may be emitted by the observable must be declared "handled" or "ignored" by the observer +// void DeclareHandledMessage(MessageType messageType) +// { +// handledMessages_.insert(messageType); +// } - void DeclareIgnoredMessage(MessageType messageType) - { - ignoredMessages_.insert(messageType); - } +// void DeclareIgnoredMessage(MessageType messageType) +// { +// ignoredMessages_.insert(messageType); +// } };
--- a/Framework/Messages/MessageBroker.cpp Mon Sep 10 12:22:26 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,56 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2018 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "MessageBroker.h" - -#include <algorithm> -#include <assert.h> -#include <vector> - -#include "IObserver.h" -#include "MessageType.h" - -namespace OrthancStone { - - void MessageBroker::EmitMessage(IObservable& from, std::set<IObserver*> observers, const IMessage& message) - { - std::vector<IObserver*> activeObservers; - std::set_intersection(observers.begin(), - observers.end(), - activeObservers_.begin(), - activeObservers_.end(), - std::back_inserter(activeObservers) - ); - - for (std::vector<IObserver*>::iterator observer = activeObservers.begin(); observer != activeObservers.end(); observer++) - { - if ((*observer)->GetHandledMessages().find(message.GetType()) != (*observer)->GetHandledMessages().end()) - { - (*observer)->HandleMessage_(from, message); - } - else - { - assert((*observer)->GetIgnoredMessages().find(message.GetType()) != (*observer)->GetIgnoredMessages().end()); // message has not been declared by Observer (this should already have been checked during registration) - } - } - } - -}
--- a/Framework/Messages/MessageBroker.h Mon Sep 10 12:22:26 2018 +0200 +++ b/Framework/Messages/MessageBroker.h Fri Sep 14 16:44:01 2018 +0200 @@ -21,23 +21,18 @@ #pragma once -#include "../StoneEnumerations.h" - #include "boost/noncopyable.hpp" -#include <map> -#include <list> #include <set> namespace OrthancStone { class IObserver; class IObservable; - class IMessage; /* * This is a central message broker. It keeps track of all observers and knows * when an observer is deleted. - * This way, it can prevent an observable to send a message to a dead observer. + * This way, it can prevent an observable to send a message to a deleted observer. */ class MessageBroker : public boost::noncopyable { @@ -56,7 +51,10 @@ activeObservers_.erase(&observer); } - void EmitMessage(IObservable& from, std::set<IObserver*> observers, const IMessage& message); + bool IsActive(IObserver* observer) + { + return activeObservers_.find(observer) != activeObservers_.end(); + } }; }
--- a/Framework/Messages/MessageType.h Mon Sep 10 12:22:26 2018 +0200 +++ b/Framework/Messages/MessageType.h Fri Sep 14 16:44:01 2018 +0200 @@ -44,6 +44,8 @@ MessageType_OrthancApi_InternalGetJsonResponseReady, MessageType_OrthancApi_InternalGetJsonResponseError, + MessageType_OrthancApi_GenericGetJson_Ready, + MessageType_OrthancApi_GenericHttpError_Ready, MessageType_OrthancApi_GetStudyIds_Ready, MessageType_OrthancApi_GetStudy_Ready, MessageType_OrthancApi_GetSeries_Ready,
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Messages/Promise.h Fri Sep 14 16:44:01 2018 +0200 @@ -0,0 +1,89 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "MessageBroker.h" +#include "ICallable.h" +#include "IMessage.h" + +#include <boost/noncopyable.hpp> + +namespace OrthancStone { + + class Promise : public boost::noncopyable + { + protected: + MessageBroker& broker_; + + ICallable* successCallable_; + ICallable* failureCallable_; + + public: + Promise(MessageBroker& broker) + : broker_(broker), + successCallable_(NULL), + failureCallable_(NULL) + { + } + + void Success(const IMessage& message) + { + // check the target is still alive in the broker + if (broker_.IsActive(successCallable_->GetObserver())) + { + successCallable_->Apply(message); + } + } + + void Failure(const IMessage& message) + { + // check the target is still alive in the broker + if (broker_.IsActive(failureCallable_->GetObserver())) + { + failureCallable_->Apply(message); + } + } + + Promise& Then(ICallable* successCallable) + { + if (successCallable_ != NULL) + { + // TODO: throw throw new "Promise may only have a single success target" + } + successCallable_ = successCallable; + return *this; + } + + Promise& Else(ICallable* failureCallable) + { + if (failureCallable_ != NULL) + { + // TODO: throw throw new "Promise may only have a single failure target" + } + failureCallable_ = failureCallable; + return *this; + } + + }; + + +}
--- a/Framework/SmartLoader.cpp Mon Sep 10 12:22:26 2018 +0200 +++ b/Framework/SmartLoader.cpp Fri Sep 14 16:44:01 2018 +0200 @@ -31,11 +31,11 @@ webService_(webService), orthancApiClient_(broker, webService) { - DeclareHandledMessage(MessageType_LayerSource_GeometryReady); - DeclareHandledMessage(MessageType_LayerSource_LayerReady); - DeclareIgnoredMessage(MessageType_LayerSource_GeometryError); - DeclareIgnoredMessage(MessageType_LayerSource_ContentChanged); - DeclareIgnoredMessage(MessageType_LayerSource_SliceChanged); +// DeclareHandledMessage(MessageType_LayerSource_GeometryReady); +// DeclareHandledMessage(MessageType_LayerSource_LayerReady); +// DeclareIgnoredMessage(MessageType_LayerSource_GeometryError); +// DeclareIgnoredMessage(MessageType_LayerSource_ContentChanged); +// DeclareIgnoredMessage(MessageType_LayerSource_SliceChanged); // DeclareHandledMessage(MessageType_OrthancApi_InternalGetJsonResponseReady); // DeclareIgnoredMessage(MessageType_OrthancApi_InternalGetJsonResponseError); @@ -65,7 +65,7 @@ } // forward messages to its own observers - IObservable::broker_.EmitMessage(from, IObservable::observers_, message); + // TODO TODO TODO IObservable::broker_.EmitMessage(from, IObservable::observers_, message); } ILayerSource* SmartLoader::GetFrame(const std::string& instanceId, unsigned int frame) @@ -78,7 +78,7 @@ // in both cases, we must be carefull about objects lifecycle !!! std::auto_ptr<OrthancFrameLayerSource> layerSource (new OrthancFrameLayerSource(IObserver::broker_, webService_)); layerSource->SetImageQuality(imageQuality_); - layerSource->RegisterObserver(*this); + //layerSource->RegisterObserverCallback(new Callable<SmartLoader, ILayerSource::GeometryReadyMessage>(*this, &SmartLoader::....)); layerSource->LoadFrame(instanceId, frame); return layerSource.release();
--- a/Framework/Toolbox/IWebService.h Mon Sep 10 12:22:26 2018 +0200 +++ b/Framework/Toolbox/IWebService.h Fri Sep 14 16:44:01 2018 +0200 @@ -23,7 +23,9 @@ #include <Core/IDynamicObject.h> #include "../../Framework/Messages/IObserver.h" +#include "../../Framework/Messages/ICallable.h" #include <string> +#include <map> #include <Core/Logging.h> namespace OrthancStone @@ -40,6 +42,37 @@ public: typedef std::map<std::string, std::string> Headers; + struct NewHttpRequestSuccessMessage: public BaseMessage<MessageType_HttpRequestSuccess> + { + const std::string& Uri; + const void* Answer; + size_t AnswerSize; + Orthanc::IDynamicObject* Payload; + NewHttpRequestSuccessMessage(const std::string& uri, + const void* answer, + size_t answerSize, + Orthanc::IDynamicObject* payload) + : BaseMessage(), + Uri(uri), + Answer(answer), + AnswerSize(answerSize), + Payload(payload) + {} + }; + + struct NewHttpRequestErrorMessage: public BaseMessage<MessageType_HttpRequestError> + { + const std::string& Uri; + Orthanc::IDynamicObject* Payload; + NewHttpRequestErrorMessage(const std::string& uri, + Orthanc::IDynamicObject* payload) + : BaseMessage(), + Uri(uri), + Payload(payload) + {} + }; + + class ICallback : public IObserver { public: @@ -76,8 +109,8 @@ ICallback(MessageBroker& broker) : IObserver(broker) { - DeclareHandledMessage(MessageType_HttpRequestError); - DeclareHandledMessage(MessageType_HttpRequestSuccess); +// DeclareHandledMessage(MessageType_HttpRequestError); +// DeclareHandledMessage(MessageType_HttpRequestSuccess); } virtual ~ICallback() { @@ -129,6 +162,13 @@ const Headers& headers, Orthanc::IDynamicObject* payload) = 0; + virtual void GetAsync(const std::string& uri, + const Headers& headers, + Orthanc::IDynamicObject* payload, + MessageHandler<IWebService::NewHttpRequestSuccessMessage>* successCallback, + MessageHandler<IWebService::NewHttpRequestErrorMessage>* failureCallback) = 0; + + virtual void SchedulePostRequest(ICallback& callback, const std::string& uri, const Headers& headers,
--- a/Framework/Toolbox/OrthancApiClient.cpp Mon Sep 10 12:22:26 2018 +0200 +++ b/Framework/Toolbox/OrthancApiClient.cpp Fri Sep 14 16:44:01 2018 +0200 @@ -32,7 +32,7 @@ Json::Value response_; InternalGetJsonResponseReadyMessage(OrthancApiClient::BaseRequest* request, - const Json::Value& response) + const Json::Value& response) : IMessage(MessageType_OrthancApi_InternalGetJsonResponseReady), request_(request), response_(response) @@ -58,7 +58,7 @@ // Once the response is ready, it will emit a message to the responseObserver // the responseObserver must handle only that message (and not all messages from the OrthancApiClient) class OrthancApiClient::BaseRequest: -// public IObserver, + // public IObserver, public IObservable, public Orthanc::IDynamicObject { @@ -71,10 +71,10 @@ public: BaseRequest( OrthancApiClient& orthanc, - IObserver& responseObserver, - const std::string& uri, - MessageType messageToEmitWhenResponseReady, - OrthancApiClient::Mode mode) + IObserver& responseObserver, + const std::string& uri, + MessageType messageToEmitWhenResponseReady, + OrthancApiClient::Mode mode) : //IObserver(orthanc.broker_), IObservable(orthanc.broker_), @@ -86,30 +86,30 @@ // this object will emit only a single message, the one the final responseObserver is expecting DeclareEmittableMessage(messageToEmitWhenResponseReady); -// // this object is observing the OrthancApi so it must handle all messages -// DeclareHandledMessage(MessageType_OrthancApi_InternalGetJsonResponseReady); -// DeclareIgnoredMessage(MessageType_OrthancApi_InternalGetJsonResponseError); + // // this object is observing the OrthancApi so it must handle all messages + // DeclareHandledMessage(MessageType_OrthancApi_InternalGetJsonResponseReady); + // DeclareIgnoredMessage(MessageType_OrthancApi_InternalGetJsonResponseError); //orthanc_.RegisterObserver(*this); - this->RegisterObserver(responseObserver); + //this->RegisterObserver(responseObserver); } virtual ~BaseRequest() {} -// // mainly maps OrthancApi internal messages to a message that is expected by the responseObserver -// virtual void HandleMessage(IObservable& from, const IMessage& message) -// { -// switch (message.GetType()) -// { -// case MessageType_OrthancApi_InternalGetJsonResponseReady: -// { -// const OrthancApiClient::InternalGetJsonResponseReadyMessage& messageReceived = dynamic_cast<const OrthancApiClient::InternalGetJsonResponseReadyMessage&>(message); -// EmitMessage(OrthancApiClient::GetJsonResponseReadyMessage(messageToEmitWhenResponseReady_, messageReceived.request_->uri_, messageReceived.response_)); -// orthanc_.ReleaseRequest(messageReceived.request_); -// }; break; -// default: -// throw MessageNotDeclaredException(message.GetType()); -// } -// } + // // mainly maps OrthancApi internal messages to a message that is expected by the responseObserver + // virtual void HandleMessage(IObservable& from, const IMessage& message) + // { + // switch (message.GetType()) + // { + // case MessageType_OrthancApi_InternalGetJsonResponseReady: + // { + // const OrthancApiClient::InternalGetJsonResponseReadyMessage& messageReceived = dynamic_cast<const OrthancApiClient::InternalGetJsonResponseReadyMessage&>(message); + // EmitMessage(OrthancApiClient::GetJsonResponseReadyMessage(messageToEmitWhenResponseReady_, messageReceived.request_->uri_, messageReceived.response_)); + // orthanc_.ReleaseRequest(messageReceived.request_); + // }; break; + // default: + // throw MessageNotDeclaredException(message.GetType()); + // } + // } }; @@ -144,8 +144,8 @@ } else { -// OrthancApiClient::InternalGetJsonResponseErrorMessage msg(request); -// that_.EmitMessage(msg); + // OrthancApiClient::InternalGetJsonResponseErrorMessage msg(request); + // that_.EmitMessage(msg); } }; break; @@ -164,9 +164,9 @@ { case OrthancApiClient::Mode_GetJson: { -// OrthancApiClient::InternalGetJsonResponseErrorMessage msg(request); -// that_.EmitMessage(msg); - // TODO: the request shall send an error message + // OrthancApiClient::InternalGetJsonResponseErrorMessage msg(request); + // that_.EmitMessage(msg); + // TODO: the request shall send an error message }; break; default: @@ -202,4 +202,61 @@ delete request; } + // performs the translation between IWebService messages and OrthancApiClient messages + // TODO: handle destruction of this object (with shared_ptr ?::delete_later ???) + class HttpResponseToJsonConverter : public IObserver, IObservable + { + std::auto_ptr<MessageHandler<OrthancApiClient::NewGetJsonResponseReadyMessage>> orthancApiSuccessCallback_; + std::auto_ptr<MessageHandler<OrthancApiClient::NewHttpErrorMessage>> orthancApiFailureCallback_; + public: + HttpResponseToJsonConverter(MessageBroker& broker, + MessageHandler<OrthancApiClient::NewGetJsonResponseReadyMessage>* orthancApiSuccessCallback, + MessageHandler<OrthancApiClient::NewHttpErrorMessage>* orthancApiFailureCallback) + : IObserver(broker), + IObservable(broker), + orthancApiSuccessCallback_(orthancApiSuccessCallback), + orthancApiFailureCallback_(orthancApiFailureCallback) + { + } + + void ConvertResponseToJson(const IWebService::NewHttpRequestSuccessMessage& message) + { + Json::Value response; + if (MessagingToolbox::ParseJson(response, message.Answer, message.AnswerSize)) + { + if (orthancApiSuccessCallback_.get() != NULL) + { + orthancApiSuccessCallback_->Apply(OrthancApiClient::NewGetJsonResponseReadyMessage(message.Uri, response)); + } + } + else if (orthancApiFailureCallback_.get() != NULL) + { + orthancApiFailureCallback_->Apply(OrthancApiClient::NewHttpErrorMessage(message.Uri)); + } + + delete this; // hack untill we find someone to take ownership of this object (https://isocpp.org/wiki/faq/freestore-mgmt#delete-this) + } + + void ConvertError(const IWebService::NewHttpRequestErrorMessage& message) + { + if (orthancApiFailureCallback_.get() != NULL) + { + orthancApiFailureCallback_->Apply(OrthancApiClient::NewHttpErrorMessage(message.Uri)); + } + + delete this; // hack untill we find someone to take ownership of this object (https://isocpp.org/wiki/faq/freestore-mgmt#delete-this) + } + }; + + void OrthancApiClient::GetJsonAsync(const std::string& uri, + MessageHandler<NewGetJsonResponseReadyMessage>* successCallback, + MessageHandler<NewHttpErrorMessage>* failureCallback) + { + HttpResponseToJsonConverter* converter = new HttpResponseToJsonConverter(broker_, successCallback, failureCallback); + orthanc_.GetAsync(uri, IWebService::Headers(), NULL, + new Callable<HttpResponseToJsonConverter, IWebService::NewHttpRequestSuccessMessage>(*converter, &HttpResponseToJsonConverter::ConvertResponseToJson), + new Callable<HttpResponseToJsonConverter, IWebService::NewHttpRequestErrorMessage>(*converter, &HttpResponseToJsonConverter::ConvertError)); + + } + }
--- a/Framework/Toolbox/OrthancApiClient.h Mon Sep 10 12:22:26 2018 +0200 +++ b/Framework/Toolbox/OrthancApiClient.h Fri Sep 14 16:44:01 2018 +0200 @@ -26,7 +26,7 @@ #include "IWebService.h" #include "../Messages/IObservable.h" - +#include "../Messages/Promise.h" namespace OrthancStone { @@ -56,6 +56,32 @@ } }; + struct NewGetJsonResponseReadyMessage : public BaseMessage<MessageType_OrthancApi_GenericGetJson_Ready> + { + Json::Value response_; + std::string uri_; + + NewGetJsonResponseReadyMessage(const std::string& uri, + const Json::Value& response) + : BaseMessage(), + response_(response), + uri_(uri) + { + } + }; + + struct NewHttpErrorMessage : public BaseMessage<MessageType_OrthancApi_GenericHttpError_Ready> + { + std::string uri_; + + NewHttpErrorMessage(const std::string& uri) + : BaseMessage(), + uri_(uri) + { + } + }; + + public: enum Mode @@ -69,13 +95,13 @@ boost::shared_ptr<WebCallback> webCallback_; // This is a PImpl pattern std::set<BaseRequest*> requestsInProgress_; -// int ScheduleGetJsonRequest(const std::string& uri); + // int ScheduleGetJsonRequest(const std::string& uri); void ReleaseRequest(BaseRequest* request); public: OrthancApiClient(MessageBroker& broker, - IWebService& orthanc); + IWebService& orthanc); virtual ~OrthancApiClient() {} // schedule a GET request expecting a JSON request. @@ -86,5 +112,11 @@ void ScheduleGetStudy(IObserver& responseObserver, const std::string& studyId) {ScheduleGetJsonRequest(responseObserver, "/studies/" + studyId, MessageType_OrthancApi_GetStudy_Ready);} void ScheduleGetSeries(IObserver& responseObserver, const std::string& seriesId) {ScheduleGetJsonRequest(responseObserver, "/series/" + seriesId, MessageType_OrthancApi_GetSeries_Ready);} + void GetJsonAsync(const std::string& uri, + MessageHandler<NewGetJsonResponseReadyMessage>* successCallback, + MessageHandler<NewHttpErrorMessage>* failureCallback = NULL); + + + }; }
--- a/Framework/Toolbox/OrthancSlicesLoader.cpp Mon Sep 10 12:22:26 2018 +0200 +++ b/Framework/Toolbox/OrthancSlicesLoader.cpp Fri Sep 14 16:44:01 2018 +0200 @@ -253,7 +253,7 @@ { case Mode_FrameGeometry: case Mode_SeriesGeometry: - that_.EmitMessage(IMessage(MessageType_SliceLoader_GeometryError)); + that_.EmitMessage(SliceGeometryErrorMessage(that_)); that_.state_ = State_Error; break; @@ -271,11 +271,11 @@ } }; - void OrthancSlicesLoader::HandleMessage(IObservable& from, const IMessage& message) - { - // forward messages to its own observers - IObservable::broker_.EmitMessage(from, IObservable::observers_, message); - } +// void OrthancSlicesLoader::HandleMessage(IObservable& from, const IMessage& message) +// { +// // forward messages to its own observers +// IObservable::broker_.EmitMessage(from, IObservable::observers_, message); +// } void OrthancSlicesLoader::NotifySliceImageSuccess(const Operation& operation, @@ -321,12 +321,12 @@ if (ok) { LOG(INFO) << "Loaded a series with " << slices_.GetSliceCount() << " slice(s)"; - EmitMessage(IMessage(MessageType_SliceLoader_GeometryReady)); + EmitMessage(SliceGeometryReadyMessage(*this)); } else { LOG(ERROR) << "This series is empty"; - EmitMessage(IMessage(MessageType_SliceLoader_GeometryError)); + EmitMessage(SliceGeometryErrorMessage(*this)); } } @@ -338,7 +338,7 @@ if (!MessagingToolbox::ParseJson(series, answer, size) || series.type() != Json::objectValue) { - EmitMessage(IMessage(MessageType_SliceLoader_GeometryError)); + EmitMessage(SliceGeometryErrorMessage(*this)); return; } @@ -385,7 +385,7 @@ if (!MessagingToolbox::ParseJson(tags, answer, size) || tags.type() != Json::objectValue) { - EmitMessage(IMessage(MessageType_SliceLoader_GeometryError)); + EmitMessage(SliceGeometryErrorMessage(*this)); return; } @@ -412,7 +412,7 @@ else { LOG(WARNING) << "Skipping invalid multi-frame instance " << instanceId; - EmitMessage(IMessage(MessageType_SliceLoader_GeometryError)); + EmitMessage(SliceGeometryErrorMessage(*this)); return; } } @@ -430,7 +430,7 @@ if (!MessagingToolbox::ParseJson(tags, answer, size) || tags.type() != Json::objectValue) { - EmitMessage(IMessage(MessageType_SliceLoader_GeometryError)); + EmitMessage(SliceGeometryErrorMessage(*this)); return; } @@ -446,12 +446,12 @@ { LOG(INFO) << "Loaded instance geometry " << instanceId; slices_.AddSlice(slice.release()); - EmitMessage(IMessage(MessageType_SliceLoader_GeometryReady)); + EmitMessage(SliceGeometryErrorMessage(*this)); } else { LOG(WARNING) << "Skipping invalid instance " << instanceId; - EmitMessage(IMessage(MessageType_SliceLoader_GeometryError)); + EmitMessage(SliceGeometryErrorMessage(*this)); } }
--- a/Framework/Toolbox/OrthancSlicesLoader.h Mon Sep 10 12:22:26 2018 +0200 +++ b/Framework/Toolbox/OrthancSlicesLoader.h Fri Sep 14 16:44:01 2018 +0200 @@ -32,7 +32,11 @@ class OrthancSlicesLoader : public IObservable { public: - struct SliceImageReadyMessage : public IMessage + + typedef OriginMessage<MessageType_SliceLoader_GeometryReady, OrthancSlicesLoader> SliceGeometryReadyMessage; + typedef OriginMessage<MessageType_SliceLoader_GeometryError, OrthancSlicesLoader> SliceGeometryErrorMessage; + + struct SliceImageReadyMessage : public BaseMessage<MessageType_SliceLoader_ImageReady> { unsigned int sliceIndex_; const Slice& slice_; @@ -43,7 +47,7 @@ const Slice& slice, std::auto_ptr<Orthanc::ImageAccessor>& image, SliceImageQuality effectiveQuality) - : IMessage(MessageType_SliceLoader_ImageReady), + : BaseMessage(), sliceIndex_(sliceIndex), slice_(slice), image_(image), @@ -52,7 +56,7 @@ } }; - struct SliceImageErrorMessage : public IMessage + struct SliceImageErrorMessage : public BaseMessage<MessageType_SliceLoader_ImageError> { const Slice& slice_; unsigned int sliceIndex_; @@ -61,7 +65,7 @@ SliceImageErrorMessage(unsigned int sliceIndex, const Slice& slice, SliceImageQuality effectiveQuality) - : IMessage(MessageType_SliceLoader_ImageError), + : BaseMessage(), slice_(slice), sliceIndex_(sliceIndex), effectiveQuality_(effectiveQuality) @@ -166,6 +170,6 @@ void ScheduleLoadSliceImage(size_t index, SliceImageQuality requestedQuality); - virtual void HandleMessage(IObservable& from, const IMessage& message); +// virtual void HandleMessage(IObservable& from, const IMessage& message); }; }
--- a/Framework/Widgets/LayerWidget.cpp Mon Sep 10 12:22:26 2018 +0200 +++ b/Framework/Widgets/LayerWidget.cpp Fri Sep 14 16:44:01 2018 +0200 @@ -365,14 +365,14 @@ IObservable(broker), started_(false) { - DeclareHandledMessage(MessageType_LayerSource_GeometryReady); - DeclareHandledMessage(MessageType_LayerSource_ContentChanged); - DeclareHandledMessage(MessageType_LayerSource_LayerReady); - DeclareHandledMessage(MessageType_LayerSource_SliceChanged); - DeclareHandledMessage(MessageType_LayerSource_GeometryError); +// DeclareHandledMessage(MessageType_LayerSource_GeometryReady); +// DeclareHandledMessage(MessageType_LayerSource_ContentChanged); +// DeclareHandledMessage(MessageType_LayerSource_LayerReady); +// DeclareHandledMessage(MessageType_LayerSource_SliceChanged); +// DeclareHandledMessage(MessageType_LayerSource_GeometryError); - DeclareEmittableMessage(MessageType_Widget_GeometryChanged); - DeclareEmittableMessage(MessageType_Widget_ContentChanged); +// DeclareEmittableMessage(MessageType_Widget_GeometryChanged); +// DeclareEmittableMessage(MessageType_Widget_ContentChanged); SetBackgroundCleared(true); } @@ -400,7 +400,7 @@ layersIndex_[layer] = index; ResetPendingScene(); - layer->RegisterObserver(*this); +// layer->RegisterObserver(*this); ResetChangedLayers(); @@ -424,7 +424,7 @@ layersIndex_[layer] = index; ResetPendingScene(); - layer->RegisterObserver(*this); +// layer->RegisterObserver(*this); InvalidateLayer(index); } @@ -529,7 +529,7 @@ changedLayers_[i] = true; //layers_[i]->ScheduleLayerCreation(slice_); } - EmitMessage(IMessage(MessageType_Widget_GeometryChanged)); + EmitMessage(GeometryChangedMessage(*this)); } void LayerWidget::InvalidateAllLayers()
--- a/Framework/Widgets/LayerWidget.h Mon Sep 10 12:22:26 2018 +0200 +++ b/Framework/Widgets/LayerWidget.h Fri Sep 14 16:44:01 2018 +0200 @@ -35,6 +35,10 @@ public IObserver, public IObservable { + public: + typedef OriginMessage<MessageType_Widget_GeometryChanged, LayerWidget> GeometryChangedMessage; + typedef OriginMessage<MessageType_Widget_ContentChanged, LayerWidget> ContentChangedMessage; + private: class Scene;
--- a/Framework/dev.h Mon Sep 10 12:22:26 2018 +0200 +++ b/Framework/dev.h Fri Sep 14 16:44:01 2018 +0200 @@ -239,7 +239,7 @@ computeRange_(computeRange), pendingSlices_(0) { - loader_.RegisterObserver(*this); + // TODO: replace with new callables loader_.RegisterObserver(*this); } void ScheduleLoadSeries(const std::string& seriesId)
--- a/Platforms/Generic/OracleWebService.h Mon Sep 10 12:22:26 2018 +0200 +++ b/Platforms/Generic/OracleWebService.h Fri Sep 14 16:44:01 2018 +0200 @@ -59,6 +59,15 @@ oracle_.Submit(new WebServiceGetCommand(broker_, callback, parameters_, uri, headers, payload, context_)); } + virtual void GetAsync(const std::string& uri, + const Headers& headers, + Orthanc::IDynamicObject* payload, + MessageHandler<IWebService::NewHttpRequestSuccessMessage>* successCallback, // takes ownership + MessageHandler<IWebService::NewHttpRequestErrorMessage>* failureCallback = NULL)// takes ownership + { + oracle_.Submit(new NewWebServiceGetCommand(broker_, successCallback, failureCallback, parameters_, uri, headers, payload, context_)); + } + virtual void SchedulePostRequest(ICallback& callback, const std::string& uri, const Headers& headers,
--- a/Platforms/Generic/WebServiceCommandBase.cpp Mon Sep 10 12:22:26 2018 +0200 +++ b/Platforms/Generic/WebServiceCommandBase.cpp Fri Sep 14 16:44:01 2018 +0200 @@ -42,7 +42,7 @@ { DeclareEmittableMessage(MessageType_HttpRequestError); DeclareEmittableMessage(MessageType_HttpRequestSuccess); - RegisterObserver(callback); + // TODO ? RegisterObserver(callback); } @@ -61,4 +61,40 @@ EmitMessage(message); } } + + NewWebServiceCommandBase::NewWebServiceCommandBase(MessageBroker& broker, + MessageHandler<IWebService::NewHttpRequestSuccessMessage>* successCallback, + MessageHandler<IWebService::NewHttpRequestErrorMessage>* failureCallback, + const Orthanc::WebServiceParameters& parameters, + const std::string& uri, + const IWebService::Headers& headers, + Orthanc::IDynamicObject* payload /* takes ownership */, + NativeStoneApplicationContext& context) : + IObservable(broker), + successCallback_(successCallback), + failureCallback_(failureCallback), + parameters_(parameters), + uri_(uri), + headers_(headers), + payload_(payload), + context_(context) + { + } + + + void NewWebServiceCommandBase::Commit() + { + NativeStoneApplicationContext::GlobalMutexLocker lock(context_); // we want to make sure that, i.e, the UpdateThread is not triggered while we are updating the "model" with the result of a WebServiceCommand + + if (success_ && successCallback_.get() != NULL) + { + successCallback_->Apply(IWebService::NewHttpRequestSuccessMessage(uri_, answer_.c_str(), answer_.size(), payload_.release())); + } + else if (!success_ && failureCallback_.get() != NULL) + { + successCallback_->Apply(IWebService::NewHttpRequestErrorMessage(uri_, payload_.release())); + } + + } + }
--- a/Platforms/Generic/WebServiceCommandBase.h Mon Sep 10 12:22:26 2018 +0200 +++ b/Platforms/Generic/WebServiceCommandBase.h Fri Sep 14 16:44:01 2018 +0200 @@ -25,6 +25,7 @@ #include "../../Framework/Toolbox/IWebService.h" #include "../../Framework/Messages/IObservable.h" +#include "../../Framework/Messages/ICallable.h" #include "../../Applications/Generic/NativeStoneApplicationContext.h" #include <Core/WebServiceParameters.h> @@ -58,4 +59,33 @@ virtual void Commit(); }; + + class NewWebServiceCommandBase : public IOracleCommand, IObservable + { + protected: + std::auto_ptr<MessageHandler<IWebService::NewHttpRequestSuccessMessage>> successCallback_; + std::auto_ptr<MessageHandler<IWebService::NewHttpRequestErrorMessage>> failureCallback_; + Orthanc::WebServiceParameters parameters_; + std::string uri_; + std::map<std::string, std::string> headers_; + std::auto_ptr<Orthanc::IDynamicObject> payload_; + bool success_; + std::string answer_; + NativeStoneApplicationContext& context_; + + public: + NewWebServiceCommandBase(MessageBroker& broker, + MessageHandler<IWebService::NewHttpRequestSuccessMessage>* successCallback, // takes ownership + MessageHandler<IWebService::NewHttpRequestErrorMessage>* failureCallback, // takes ownership + const Orthanc::WebServiceParameters& parameters, + const std::string& uri, + const std::map<std::string, std::string>& headers, + Orthanc::IDynamicObject* payload /* takes ownership */, + NativeStoneApplicationContext& context); + + virtual void Execute() = 0; + + virtual void Commit(); + }; + }
--- a/Platforms/Generic/WebServiceGetCommand.cpp Mon Sep 10 12:22:26 2018 +0200 +++ b/Platforms/Generic/WebServiceGetCommand.cpp Fri Sep 14 16:44:01 2018 +0200 @@ -51,4 +51,31 @@ success_ = client.Apply(answer_); } + NewWebServiceGetCommand::NewWebServiceGetCommand(MessageBroker& broker, + MessageHandler<IWebService::NewHttpRequestSuccessMessage>* successCallback, // takes ownership + MessageHandler<IWebService::NewHttpRequestErrorMessage>* failureCallback, // takes ownership + const Orthanc::WebServiceParameters& parameters, + const std::string& uri, + const IWebService::Headers& headers, + Orthanc::IDynamicObject* payload /* takes ownership */, + NativeStoneApplicationContext& context) : + NewWebServiceCommandBase(broker, successCallback, failureCallback, parameters, uri, headers, payload, context) + { + } + + + void NewWebServiceGetCommand::Execute() + { + Orthanc::HttpClient client(parameters_, uri_); + client.SetTimeout(60); + client.SetMethod(Orthanc::HttpMethod_Get); + + for (IWebService::Headers::const_iterator it = headers_.begin(); it != headers_.end(); it++ ) + { + client.AddHeader(it->first, it->second); + } + + success_ = client.Apply(answer_); + } + }
--- a/Platforms/Generic/WebServiceGetCommand.h Mon Sep 10 12:22:26 2018 +0200 +++ b/Platforms/Generic/WebServiceGetCommand.h Fri Sep 14 16:44:01 2018 +0200 @@ -13,7 +13,7 @@ * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ @@ -38,4 +38,20 @@ virtual void Execute(); }; + + class NewWebServiceGetCommand : public NewWebServiceCommandBase + { + public: + NewWebServiceGetCommand(MessageBroker& broker, + MessageHandler<IWebService::NewHttpRequestSuccessMessage>* successCallback, // takes ownership + MessageHandler<IWebService::NewHttpRequestErrorMessage>* failureCallback, // takes ownership + const Orthanc::WebServiceParameters& parameters, + const std::string& uri, + const IWebService::Headers& headers, + Orthanc::IDynamicObject* payload /* takes ownership */, + NativeStoneApplicationContext& context); + + virtual void Execute(); + }; + }
--- a/Platforms/Wasm/WasmWebService.cpp Mon Sep 10 12:22:26 2018 +0200 +++ b/Platforms/Wasm/WasmWebService.cpp Fri Sep 14 16:44:01 2018 +0200 @@ -11,7 +11,13 @@ const char* uri, const char* headersInJsonString, void* payload); - + + extern void WasmWebService_NewScheduleGetRequest(void* callableSuccess, + void* callableFailure, + const char* uri, + const char* headersInJsonString, + void* payload); + extern void WasmWebService_SchedulePostRequest(void* callback, const char* uri, const char* headersInJsonString, @@ -51,6 +57,38 @@ } } + void EMSCRIPTEN_KEEPALIVE WasmWebService_NewNotifyError(void* failureCallable, + const char* uri, + void* payload) + { + if (failureCallable == NULL) + { + throw; + } + else + { + reinterpret_cast<OrthancStone::MessageHandler<OrthancStone::IWebService::NewHttpRequestErrorMessage>*>(failureCallable)-> + Apply(OrthancStone::IWebService::NewHttpRequestErrorMessage(uri, reinterpret_cast<Orthanc::IDynamicObject*>(payload))); + } + } + + void EMSCRIPTEN_KEEPALIVE WasmWebService_NewNotifySuccess(void* successCallable, + const char* uri, + const void* body, + size_t bodySize, + void* payload) + { + if (successCallable == NULL) + { + throw; + } + else + { + reinterpret_cast<OrthancStone::MessageHandler<OrthancStone::IWebService::NewHttpRequestSuccessMessage>*>(successCallable)-> + Apply(OrthancStone::IWebService::NewHttpRequestSuccessMessage(uri, body, bodySize, reinterpret_cast<Orthanc::IDynamicObject*>(payload))); + } + } + void EMSCRIPTEN_KEEPALIVE WasmWebService_SetBaseUri(const char* baseUri) { OrthancStone::WasmWebService::GetInstance().SetBaseUri(baseUri); @@ -119,4 +157,17 @@ WasmWebService_SchedulePostRequest(&callback, uri.c_str(), headersInJsonString.c_str(), body.c_str(), body.size(), payload); } + + void WasmWebService::GetAsync(const std::string& relativeUri, + const Headers& headers, + Orthanc::IDynamicObject* payload, + MessageHandler<IWebService::NewHttpRequestSuccessMessage>* successCallable, + MessageHandler<IWebService::NewHttpRequestErrorMessage>* failureCallable) + { + std::string uri = baseUri_ + relativeUri; + std::string headersInJsonString; + ToJsonString(headersInJsonString, headers); + WasmWebService_NewScheduleGetRequest(successCallable, failureCallable, uri.c_str(), headersInJsonString.c_str(), payload); + } + }
--- a/Platforms/Wasm/WasmWebService.h Mon Sep 10 12:22:26 2018 +0200 +++ b/Platforms/Wasm/WasmWebService.h Fri Sep 14 16:44:01 2018 +0200 @@ -48,6 +48,12 @@ const std::string& body, Orthanc::IDynamicObject* payload); + virtual void GetAsync(const std::string& relativeUri, + const Headers& headers, + Orthanc::IDynamicObject* payload, + MessageHandler<IWebService::NewHttpRequestSuccessMessage>* successCallback, + MessageHandler<IWebService::NewHttpRequestErrorMessage>* failureCallback); + virtual void Start() { }
--- a/Platforms/Wasm/WasmWebService.js Mon Sep 10 12:22:26 2018 +0200 +++ b/Platforms/Wasm/WasmWebService.js Fri Sep 14 16:44:01 2018 +0200 @@ -30,6 +30,40 @@ xhr.send(); }, + WasmWebService_NewScheduleGetRequest: function(callableSuccess, callableFailure, url, headersInJsonString, payload) { + // Directly use XMLHttpRequest (no jQuery) to retrieve the raw binary data + // http://www.henryalgus.com/reading-binary-files-using-jquery-ajax/ + var xhr = new XMLHttpRequest(); + var url_ = UTF8ToString(url); + var headersInJsonString_ = UTF8ToString(headersInJsonString); + + xhr.open('GET', url_, true); + xhr.responseType = 'arraybuffer'; + var headers = JSON.parse(headersInJsonString_); + for (var key in headers) { + xhr.setRequestHeader(key, headers[key]); + } + //console.log(xhr); + xhr.onreadystatechange = function() { + if (this.readyState == XMLHttpRequest.DONE) { + if (xhr.status === 200) { + // TODO - Is "new Uint8Array()" necessary? This copies the + // answer to the WebAssembly stack, hence necessitating + // increasing the TOTAL_STACK parameter of Emscripten + console.log("WasmWebService success", this.response); + WasmWebService_NewNotifySuccess(callableSuccess, url_, new Uint8Array(this.response), + this.response.byteLength, payload); + console.log("WasmWebService success 2", this.response); + } else { + console.log("WasmWebService failed"); + WasmWebService_NewNotifyError(callableFailure, url_, payload); + } + } + } + + xhr.send(); + }, + WasmWebService_SchedulePostRequest: function(callback, url, headersInJsonString, body, bodySize, payload) { var xhr = new XMLHttpRequest(); var url_ = UTF8ToString(url);
--- a/Platforms/Wasm/wasm-application-runner.ts Mon Sep 10 12:22:26 2018 +0200 +++ b/Platforms/Wasm/wasm-application-runner.ts Fri Sep 14 16:44:01 2018 +0200 @@ -10,6 +10,8 @@ // global functions var WasmWebService_NotifyError: Function = null; var WasmWebService_NotifySuccess: Function = null; +var WasmWebService_NewNotifyError: Function = null; +var WasmWebService_NewNotifySuccess: Function = null; var WasmWebService_SetBaseUri: Function = null; var NotifyUpdateContent: Function = null; var SetStartupParameter: Function = null; @@ -95,6 +97,8 @@ WasmWebService_NotifySuccess = StoneFrameworkModule.cwrap('WasmWebService_NotifySuccess', null, ['number', 'string', 'array', 'number', 'number']); WasmWebService_NotifyError = StoneFrameworkModule.cwrap('WasmWebService_NotifyError', null, ['number', 'string', 'number']); + WasmWebService_NewNotifySuccess = StoneFrameworkModule.cwrap('WasmWebService_NewNotifySuccess', null, ['number', 'string', 'array', 'number', 'number']); + WasmWebService_NewNotifyError = StoneFrameworkModule.cwrap('WasmWebService_NewNotifyError', null, ['number', 'string', 'number']); WasmWebService_SetBaseUri = StoneFrameworkModule.cwrap('WasmWebService_SetBaseUri', null, ['string']); NotifyUpdateContent = StoneFrameworkModule.cwrap('NotifyUpdateContent', null, []);
--- a/Resources/CMake/OrthancStoneConfiguration.cmake Mon Sep 10 12:22:26 2018 +0200 +++ b/Resources/CMake/OrthancStoneConfiguration.cmake Fri Sep 14 16:44:01 2018 +0200 @@ -280,11 +280,13 @@ ${ORTHANC_STONE_ROOT}/Framework/Widgets/WidgetBase.cpp ${ORTHANC_STONE_ROOT}/Framework/Widgets/WorldSceneWidget.cpp + ${ORTHANC_STONE_ROOT}/Framework/Messages/ICallable.h ${ORTHANC_STONE_ROOT}/Framework/Messages/IMessage.h ${ORTHANC_STONE_ROOT}/Framework/Messages/IObservable.h ${ORTHANC_STONE_ROOT}/Framework/Messages/IObserver.h - ${ORTHANC_STONE_ROOT}/Framework/Messages/MessageBroker.cpp + ${ORTHANC_STONE_ROOT}/Framework/Messages/MessageBroker.h ${ORTHANC_STONE_ROOT}/Framework/Messages/MessageType.h + ${ORTHANC_STONE_ROOT}/Framework/Messages/Promise.h ${ORTHANC_ROOT}/Plugins/Samples/Common/DicomPath.cpp ${ORTHANC_ROOT}/Plugins/Samples/Common/IOrthancConnection.cpp
--- a/UnitTestsSources/TestMessageBroker.cpp Mon Sep 10 12:22:26 2018 +0200 +++ b/UnitTestsSources/TestMessageBroker.cpp Fri Sep 14 16:44:01 2018 +0200 @@ -1,158 +1,158 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2018 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ +///** +// * Stone of Orthanc +// * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics +// * Department, University Hospital of Liege, Belgium +// * Copyright (C) 2017-2018 Osimis S.A., Belgium +// * +// * This program is free software: you can redistribute it and/or +// * modify it under the terms of the GNU Affero General Public License +// * as published by the Free Software Foundation, either version 3 of +// * the License, or (at your option) any later version. +// * +// * This program is distributed in the hope that it will be useful, but +// * WITHOUT ANY WARRANTY; without even the implied warranty of +// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// * Affero General Public License for more details. +// * +// * You should have received a copy of the GNU Affero General Public License +// * along with this program. If not, see <http://www.gnu.org/licenses/>. +// **/ -#include "gtest/gtest.h" +//#include "gtest/gtest.h" -#include "../Framework/Messages/MessageBroker.h" -#include "../Framework/Messages/IMessage.h" -#include "../Framework/Messages/IObservable.h" -#include "../Framework/Messages/IObserver.h" -#include "../Framework/StoneEnumerations.h" +//#include "../Framework/Messages/MessageBroker.h" +//#include "../Framework/Messages/IMessage.h" +//#include "../Framework/Messages/IObservable.h" +//#include "../Framework/Messages/IObserver.h" +//#include "../Framework/StoneEnumerations.h" -static int test1Counter = 0; -static int test2Counter = 0; -class MyFullObserver : public OrthancStone::IObserver -{ +//static int test1Counter = 0; +//static int test2Counter = 0; +//class MyFullObserver : public OrthancStone::IObserver +//{ -public: - MyFullObserver(OrthancStone::MessageBroker& broker) - : OrthancStone::IObserver(broker) - { - DeclareHandledMessage(OrthancStone::MessageType_Test1); - DeclareIgnoredMessage(OrthancStone::MessageType_Test2); - } +//public: +// MyFullObserver(OrthancStone::MessageBroker& broker) +// : OrthancStone::IObserver(broker) +// { +//// DeclareHandledMessage(OrthancStone::MessageType_Test1); +//// DeclareIgnoredMessage(OrthancStone::MessageType_Test2); +// } - void HandleMessage(OrthancStone::IObservable& from, const OrthancStone::IMessage& message) { - switch (message.GetType()) - { - case OrthancStone::MessageType_Test1: - test1Counter++; - break; - case OrthancStone::MessageType_Test2: - test2Counter++; - break; - default: - throw OrthancStone::MessageNotDeclaredException(message.GetType()); - } - } +// void HandleMessage(OrthancStone::IObservable& from, const OrthancStone::IMessage& message) { +// switch (message.GetType()) +// { +// case OrthancStone::MessageType_Test1: +// test1Counter++; +// break; +// case OrthancStone::MessageType_Test2: +// test2Counter++; +// break; +// default: +// throw OrthancStone::MessageNotDeclaredException(message.GetType()); +// } +// } -}; +//}; -class MyPartialObserver : public OrthancStone::IObserver -{ +//class MyPartialObserver : public OrthancStone::IObserver +//{ -public: - MyPartialObserver(OrthancStone::MessageBroker& broker) - : OrthancStone::IObserver(broker) - { - DeclareHandledMessage(OrthancStone::MessageType_Test1); - // don't declare Test2 on purpose - } +//public: +// MyPartialObserver(OrthancStone::MessageBroker& broker) +// : OrthancStone::IObserver(broker) +// { +//// DeclareHandledMessage(OrthancStone::MessageType_Test1); +// // don't declare Test2 on purpose +// } - void HandleMessage(OrthancStone::IObservable& from, const OrthancStone::IMessage& message) { - switch (message.GetType()) - { - case OrthancStone::MessageType_Test1: - test1Counter++; - break; - case OrthancStone::MessageType_Test2: - test2Counter++; - break; - default: - throw OrthancStone::MessageNotDeclaredException(message.GetType()); - } - } +// void HandleMessage(OrthancStone::IObservable& from, const OrthancStone::IMessage& message) { +// switch (message.GetType()) +// { +// case OrthancStone::MessageType_Test1: +// test1Counter++; +// break; +// case OrthancStone::MessageType_Test2: +// test2Counter++; +// break; +// default: +// throw OrthancStone::MessageNotDeclaredException(message.GetType()); +// } +// } -}; +//}; -class MyObservable : public OrthancStone::IObservable -{ +//class MyObservable : public OrthancStone::IObservable +//{ -public: - MyObservable(OrthancStone::MessageBroker& broker) - : OrthancStone::IObservable(broker) - { - DeclareEmittableMessage(OrthancStone::MessageType_Test1); - DeclareEmittableMessage(OrthancStone::MessageType_Test2); - } +//public: +// MyObservable(OrthancStone::MessageBroker& broker) +// : OrthancStone::IObservable(broker) +// { +// DeclareEmittableMessage(OrthancStone::MessageType_Test1); +// DeclareEmittableMessage(OrthancStone::MessageType_Test2); +// } -}; +//}; -TEST(MessageBroker, NormalUsage) -{ - OrthancStone::MessageBroker broker; - MyObservable observable(broker); +//TEST(MessageBroker, NormalUsage) +//{ +// OrthancStone::MessageBroker broker; +// MyObservable observable(broker); - test1Counter = 0; +// test1Counter = 0; - // no observers have been registered -> nothing shall happen - observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1)); +// // no observers have been registered -> nothing shall happen +// observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1)); - ASSERT_EQ(0, test1Counter); +// ASSERT_EQ(0, test1Counter); - // register an observer, check it is called - MyFullObserver fullObserver(broker); - ASSERT_NO_THROW(observable.RegisterObserver(fullObserver)); +// // register an observer, check it is called +// MyFullObserver fullObserver(broker); +// ASSERT_NO_THROW(observable.RegisterObserver(fullObserver)); - observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1)); +// observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1)); - ASSERT_EQ(1, test1Counter); +// ASSERT_EQ(1, test1Counter); - // register an invalid observer, check it raises an exception - MyPartialObserver partialObserver(broker); - ASSERT_THROW(observable.RegisterObserver(partialObserver), OrthancStone::MessageNotDeclaredException); +// // register an invalid observer, check it raises an exception +// MyPartialObserver partialObserver(broker); +// ASSERT_THROW(observable.RegisterObserver(partialObserver), OrthancStone::MessageNotDeclaredException); - // check an exception is thrown when the observable emits an undeclared message - ASSERT_THROW(observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_LayerSource_GeometryReady)), OrthancStone::MessageNotDeclaredException); +// // check an exception is thrown when the observable emits an undeclared message +// ASSERT_THROW(observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_LayerSource_GeometryReady)), OrthancStone::MessageNotDeclaredException); - // unregister the observer, make sure nothing happens afterwards - observable.UnregisterObserver(fullObserver); - observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1)); - ASSERT_EQ(1, test1Counter); -} +// // unregister the observer, make sure nothing happens afterwards +// observable.UnregisterObserver(fullObserver); +// observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1)); +// ASSERT_EQ(1, test1Counter); +//} -TEST(MessageBroker, DeleteObserverWhileRegistered) -{ - OrthancStone::MessageBroker broker; - MyObservable observable(broker); +//TEST(MessageBroker, DeleteObserverWhileRegistered) +//{ +// OrthancStone::MessageBroker broker; +// MyObservable observable(broker); - test1Counter = 0; +// test1Counter = 0; - { - // register an observer, check it is called - MyFullObserver observer(broker); - observable.RegisterObserver(observer); +// { +// // register an observer, check it is called +// MyFullObserver observer(broker); +// observable.RegisterObserver(observer); - observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1)); +// observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1)); - ASSERT_EQ(1, test1Counter); - } +// ASSERT_EQ(1, test1Counter); +// } - // at this point, the observer has been deleted, the handle shall not be called again (and it shall not crash !) - observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1)); +// // at this point, the observer has been deleted, the handle shall not be called again (and it shall not crash !) +// observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1)); - ASSERT_EQ(1, test1Counter); -} +// ASSERT_EQ(1, test1Counter); +//}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/TestMessageBroker2.cpp Fri Sep 14 16:44:01 2018 +0200 @@ -0,0 +1,691 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "gtest/gtest.h" + +#include "Framework/Messages/MessageBroker.h" + +#include <boost/noncopyable.hpp> +#include <boost/function.hpp> +#include <boost/bind.hpp> + +#include <string> +#include <map> +#include <set> + +int testCounter = 0; +namespace { + + class IObserver; + class IObservable; + class Promise; + + enum MessageType + { + MessageType_Test1, + MessageType_Test2, + + MessageType_CustomMessage, + MessageType_LastGenericStoneMessage + }; + + struct IMessage : public boost::noncopyable + { + MessageType messageType_; + public: + IMessage(const MessageType& messageType) + : messageType_(messageType) + {} + virtual ~IMessage() {} + + virtual int GetType() const {return messageType_;} + }; + + + struct ICustomMessage : public IMessage + { + int customMessageType_; + public: + ICustomMessage(int customMessageType) + : IMessage(MessageType_CustomMessage), + customMessageType_(customMessageType) + {} + virtual ~ICustomMessage() {} + + virtual int GetType() const {return customMessageType_;} + }; + + + // This is referencing an object and member function that can be notified + // by an IObservable. The object must derive from IO + // The member functions must be of type "void Function(const IMessage& message)" or reference a derived class of IMessage + class ICallable : public boost::noncopyable + { + public: + virtual ~ICallable() + { + } + + virtual void Apply(const IMessage& message) = 0; + + virtual MessageType GetMessageType() const = 0; + virtual IObserver* GetObserver() const = 0; + }; + + template <typename TObserver, + typename TMessage> + class Callable : public ICallable + { + private: + typedef void (TObserver::* MemberFunction) (const TMessage&); + + TObserver& observer_; + MemberFunction function_; + + public: + Callable(TObserver& observer, + MemberFunction function) : + observer_(observer), + function_(function) + { + } + + void ApplyInternal(const TMessage& message) + { + (observer_.*function_) (message); + } + + virtual void Apply(const IMessage& message) + { + ApplyInternal(dynamic_cast<const TMessage&>(message)); + } + + virtual MessageType GetMessageType() const + { + return static_cast<MessageType>(TMessage::Type); + } + + virtual IObserver* GetObserver() const + { + return &observer_; + } + }; + + + + + /* + * This is a central message broker. It keeps track of all observers and knows + * when an observer is deleted. + * This way, it can prevent an observable to send a message to a delete observer. + */ + class MessageBroker : public boost::noncopyable + { + + std::set<IObserver*> activeObservers_; // the list of observers that are currently alive (that have not been deleted) + + public: + + void Register(IObserver& observer) + { + activeObservers_.insert(&observer); + } + + void Unregister(IObserver& observer) + { + activeObservers_.erase(&observer); + } + + bool IsActive(IObserver* observer) + { + return activeObservers_.find(observer) != activeObservers_.end(); + } + }; + + + class Promise : public boost::noncopyable + { + protected: + MessageBroker& broker_; + + ICallable* successCallable_; + ICallable* failureCallable_; + + public: + Promise(MessageBroker& broker) + : broker_(broker), + successCallable_(NULL), + failureCallable_(NULL) + { + } + + void Success(const IMessage& message) + { + // check the target is still alive in the broker + if (broker_.IsActive(successCallable_->GetObserver())) + { + successCallable_->Apply(message); + } + } + + void Failure(const IMessage& message) + { + // check the target is still alive in the broker + if (broker_.IsActive(failureCallable_->GetObserver())) + { + failureCallable_->Apply(message); + } + } + + Promise& Then(ICallable* successCallable) + { + if (successCallable_ != NULL) + { + // TODO: throw throw new "Promise may only have a single success target" + } + successCallable_ = successCallable; + return *this; + } + + Promise& Else(ICallable* failureCallable) + { + if (failureCallable_ != NULL) + { + // TODO: throw throw new "Promise may only have a single failure target" + } + failureCallable_ = failureCallable; + return *this; + } + + }; + + class IObserver : public boost::noncopyable + { + protected: + MessageBroker& broker_; + + public: + IObserver(MessageBroker& broker) + : broker_(broker) + { + broker_.Register(*this); + } + + virtual ~IObserver() + { + broker_.Unregister(*this); + } + + }; + + + class IObservable : public boost::noncopyable + { + protected: + MessageBroker& broker_; + + typedef std::map<int, std::set<ICallable*> > Callables; + Callables callables_; + public: + + IObservable(MessageBroker& broker) + : broker_(broker) + { + } + + virtual ~IObservable() + { + for (Callables::const_iterator it = callables_.begin(); + it != callables_.end(); ++it) + { + for (std::set<ICallable*>::const_iterator + it2 = it->second.begin(); it2 != it->second.end(); ++it2) + { + delete *it2; + } + } + } + + void Register(ICallable* callable) + { + MessageType messageType = callable->GetMessageType(); + + callables_[messageType].insert(callable); + } + + void EmitMessage(const IMessage& message) + { + Callables::const_iterator found = callables_.find(message.GetType()); + + if (found != callables_.end()) + { + for (std::set<ICallable*>::const_iterator + it = found->second.begin(); it != found->second.end(); ++it) + { + if (broker_.IsActive((*it)->GetObserver())) + { + (*it)->Apply(message); + } + } + } + } + + }; + + + enum CustomMessageType + { + CustomMessageType_First = MessageType_LastGenericStoneMessage + 1, + + CustomMessageType_Completed, + CustomMessageType_Increment + }; + + class MyObservable : public IObservable + { + public: + struct MyCustomMessage: public ICustomMessage + { + int payload_; + enum + { + Type = CustomMessageType_Completed + }; + + MyCustomMessage(int payload) + : ICustomMessage(Type), + payload_(payload) + {} + }; + + MyObservable(MessageBroker& broker) + : IObservable(broker) + {} + + }; + + class MyObserver : public IObserver + { + public: + MyObserver(MessageBroker& broker) + : IObserver(broker) + {} + + void HandleCompletedMessage(const MyObservable::MyCustomMessage& message) + { + testCounter += message.payload_; + } + + }; + + + class MyPromiseSource : public IObservable + { + Promise* currentPromise_; + public: + struct MyPromiseMessage: public ICustomMessage + { + int increment; + enum + { + Type = CustomMessageType_Increment + }; + + MyPromiseMessage(int increment) + : ICustomMessage(Type), + increment(increment) + {} + }; + + MyPromiseSource(MessageBroker& broker) + : IObservable(broker), + currentPromise_(NULL) + {} + + Promise& StartSomethingAsync() + { + currentPromise_ = new Promise(broker_); + return *currentPromise_; + } + + void CompleteSomethingAsyncWithSuccess(int payload) + { + currentPromise_->Success(MyPromiseMessage(payload)); + delete currentPromise_; + } + + void CompleteSomethingAsyncWithFailure(int payload) + { + currentPromise_->Failure(MyPromiseMessage(payload)); + delete currentPromise_; + } + }; + + + class MyPromiseTarget : public IObserver + { + public: + MyPromiseTarget(MessageBroker& broker) + : IObserver(broker) + {} + + void IncrementCounter(const MyPromiseSource::MyPromiseMessage& args) + { + testCounter += args.increment; + } + + void DecrementCounter(const MyPromiseSource::MyPromiseMessage& args) + { + testCounter -= args.increment; + } + }; +} + + +TEST(MessageBroker2, TestPermanentConnectionSimpleUseCase) +{ + MessageBroker broker; + MyObservable observable(broker); + MyObserver observer(broker); + + // create a permanent connection between an observable and an observer + observable.Register(new Callable<MyObserver, MyObservable::MyCustomMessage>(observer, &MyObserver::HandleCompletedMessage)); + + testCounter = 0; + observable.EmitMessage(MyObservable::MyCustomMessage(12)); + ASSERT_EQ(12, testCounter); + + // the connection is permanent; if we emit the same message again, the observer will be notified again + testCounter = 0; + observable.EmitMessage(MyObservable::MyCustomMessage(20)); + ASSERT_EQ(20, testCounter); +} + +TEST(MessageBroker2, TestPermanentConnectionDeleteObserver) +{ + MessageBroker broker; + MyObservable observable(broker); + MyObserver* observer = new MyObserver(broker); + + // create a permanent connection between an observable and an observer + observable.Register(new Callable<MyObserver, MyObservable::MyCustomMessage>(*observer, &MyObserver::HandleCompletedMessage)); + + testCounter = 0; + observable.EmitMessage(MyObservable::MyCustomMessage(12)); + ASSERT_EQ(12, testCounter); + + // delete the observer and check that the callback is not called anymore + delete observer; + + // the connection is permanent; if we emit the same message again, the observer will be notified again + testCounter = 0; + observable.EmitMessage(MyObservable::MyCustomMessage(20)); + ASSERT_EQ(0, testCounter); +} + + +TEST(MessageBroker2, TestPromiseSuccessFailure) +{ + MessageBroker broker; + MyPromiseSource source(broker); + MyPromiseTarget target(broker); + + // test a successful promise + source.StartSomethingAsync() + .Then(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(target, &MyPromiseTarget::IncrementCounter)) + .Else(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(target, &MyPromiseTarget::DecrementCounter)); + + testCounter = 0; + source.CompleteSomethingAsyncWithSuccess(10); + ASSERT_EQ(10, testCounter); + + // test a failing promise + source.StartSomethingAsync() + .Then(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(target, &MyPromiseTarget::IncrementCounter)) + .Else(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(target, &MyPromiseTarget::DecrementCounter)); + + testCounter = 0; + source.CompleteSomethingAsyncWithFailure(15); + ASSERT_EQ(-15, testCounter); +} + +TEST(MessageBroker2, TestPromiseDeleteTarget) +{ + MessageBroker broker; + MyPromiseSource source(broker); + MyPromiseTarget* target = new MyPromiseTarget(broker); + + // create the promise + source.StartSomethingAsync() + .Then(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(*target, &MyPromiseTarget::IncrementCounter)) + .Else(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(*target, &MyPromiseTarget::DecrementCounter)); + + // delete the promise target + delete target; + + // trigger the promise, make sure it does not throw and does not call the callback + testCounter = 0; + source.CompleteSomethingAsyncWithSuccess(10); + ASSERT_EQ(0, testCounter); + + // test a failing promise + source.StartSomethingAsync() + .Then(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(*target, &MyPromiseTarget::IncrementCounter)) + .Else(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(*target, &MyPromiseTarget::DecrementCounter)); + + testCounter = 0; + source.CompleteSomethingAsyncWithFailure(15); + ASSERT_EQ(0, testCounter); +} + + + +//#include <stdio.h> +//#include <boost/noncopyable.hpp> + +//#include <string> +//#include <memory> +//#include <map> +//#include <set> + +//enum MessageType +//{ +// MessageType_SeriesDownloaded = 1 +//}; + + +//class IMessage : public boost::noncopyable +//{ +//private: +// MessageType type_; + +//public: +// IMessage(MessageType type) : +// type_(type) +// { +// } + +// virtual ~IMessage() +// { +// } + +// MessageType GetMessageType() const +// { +// return type_; +// } +//}; + + +//class IObserver : public boost::noncopyable +//{ +//public: +// virtual ~IObserver() +// { +// } +//}; + + +//class SeriesDownloadedMessage : public IMessage +//{ +//private: +// std::string value_; + +//public: +// enum +// { +// Type = MessageType_SeriesDownloaded +// }; + +// SeriesDownloadedMessage(const std::string& value) : +// IMessage(static_cast<MessageType>(Type)), +// value_(value) +// { +// } + +// const std::string& GetValue() const +// { +// return value_; +// } +//}; + + +//class MyObserver : public IObserver +//{ +//public: +// void OnSeriesDownloaded(const SeriesDownloadedMessage& message) +// { +// printf("received: [%s]\n", message.GetValue().c_str()); +// } +//}; + + + +//class ICallable : public boost::noncopyable // ne peut referencer que les classes de base +//{ +//public: +// virtual ~ICallable() +// { +// } + +// virtual void Apply(const IMessage& message) = 0; + +// virtual MessageType GetMessageType() const = 0; +//}; + + + +//template <typename Observer, +// typename Message> +//class Callable : public ICallable +//{ +//private: +// typedef void (Observer::* MemberFunction) (const Message&); + +// Observer& observer_; +// MemberFunction function_; + +//public: +// Callable(Observer& observer, +// MemberFunction function) : +// observer_(observer), +// function_(function) +// { +// } + +// void ApplyInternal(const Message& message) +// { +// (observer_.*function_) (message); +// } + +// virtual void Apply(const IMessage& message) +// { +// ApplyInternal(dynamic_cast<const Message&>(message)); +// } + +// virtual MessageType GetMessageType() const +// { +// return static_cast<MessageType>(Message::Type); +// } +//}; + + + +//class IObservable : public boost::noncopyable +//{ +//private: +// typedef std::map<MessageType, std::set<ICallable*> > Callables; + +// Callables callables_; + +//public: +// virtual ~IObservable() +// { +// for (Callables::const_iterator it = callables_.begin(); +// it != callables_.end(); ++it) +// { +// for (std::set<ICallable*>::const_iterator +// it2 = it->second.begin(); it2 != it->second.end(); ++it2) +// { +// delete *it2; +// } +// } +// } + +// void Register(ICallable* callable) +// { +// MessageType type = callable->GetMessageType(); + +// callables_[type].insert(callable); +// } + +// void Emit(const IMessage& message) const +// { +// Callables::const_iterator found = callables_.find(message.GetMessageType()); + +// if (found != callables_.end()) +// { +// for (std::set<ICallable*>::const_iterator +// it = found->second.begin(); it != found->second.end(); ++it) +// { +// (*it)->Apply(message); +// } +// } +// } +//}; + + + + +//int main() +//{ +// MyObserver observer; + +// SeriesDownloadedMessage message("coucou"); + +// IObservable observable; +// observable.Register(new Callable<MyObserver, SeriesDownloadedMessage>(observer, &MyObserver::OnSeriesDownloaded)); +// observable.Register(new Callable<MyObserver, SeriesDownloadedMessage>(observer, &MyObserver::OnSeriesDownloaded)); + +// SeriesDownloadedMessage message2("hello"); +// observable.Emit(message2); + +// printf("%d\n", SeriesDownloadedMessage::Type); +//}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/TestMessageBroker2_connect_ok.cpp Fri Sep 14 16:44:01 2018 +0200 @@ -0,0 +1,226 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "gtest/gtest.h" + +#include <boost/noncopyable.hpp> +#include <boost/function.hpp> +#include <boost/bind.hpp> + +#include <string> +#include <map> +#include <set> + +int testCounter = 0; +namespace { + + enum MessageType + { + // used in unit tests only + MessageType_Test1, + MessageType_Test2, + + MessageType_LastGenericStoneMessage + }; + + struct IMessage : public boost::noncopyable + { + MessageType messageType_; + public: + IMessage(const MessageType& messageType) + : messageType_(messageType) + {} + virtual ~IMessage() {} + + MessageType GetType() const {return messageType_;} + }; + + + class IObserver; + class IObservable; + + /* + * This is a central message broker. It keeps track of all observers and knows + * when an observer is deleted. + * This way, it can prevent an observable to send a message to a dead observer. + */ + class MessageBroker : public boost::noncopyable + { + + std::set<IObserver*> activeObservers_; // the list of observers that are currently alive (that have not been deleted) + + public: + + void Register(IObserver& observer) + { + activeObservers_.insert(&observer); + } + + void Unregister(IObserver& observer) + { + activeObservers_.erase(&observer); + } + + void EmitMessage(IObservable& from, std::set<IObserver*> observers, const IMessage& message); + }; + + + class IObserver : public boost::noncopyable + { + protected: + MessageBroker& broker_; + + public: + IObserver(MessageBroker& broker) + : broker_(broker) + { + broker_.Register(*this); + } + + virtual ~IObserver() + { + broker_.Unregister(*this); + } + + void HandleMessage_(IObservable &from, const IMessage &message) + { + + HandleMessage(from, message); + } + + virtual void HandleMessage(IObservable& from, const IMessage& message) = 0; + + + protected: + + + }; + +// struct ICallableObserver +// { +// IObserver* observer; +// }; + +// typedef void (IObserver::*ObserverSingleMesssageHandler)(IObservable& from, const IMessage& message); + +// template <typename TObserver> +// struct CallableObserver : public ICallableObserver +// { +// void (TObserver::*ptrToMemberHandler)(IObservable& from, const IMessage& message); +// }; + + struct CallableObserver + { + IObserver* observer; + boost::function<void (IObservable& from, const IMessage& message)> f; + }; + + class IObservable : public boost::noncopyable + { + protected: + MessageBroker& broker_; + + std::set<IObserver*> observers_; + + std::map<MessageType, std::set<CallableObserver*> > callables_; + public: + + IObservable(MessageBroker& broker) + : broker_(broker) + { + } + virtual ~IObservable() + { + } + + void EmitMessage(const IMessage& message) + { + //broker_.EmitMessage(*this, observers_, message); + + // TODO check if observer is still alive and call ! + CallableObserver* callable = *(callables_[message.GetType()].begin()); + callable->f(*this, message); + } + + void RegisterObserver(IObserver& observer) + { + observers_.insert(&observer); + } + + void UnregisterObserver(IObserver& observer) + { + observers_.erase(&observer); + } + + + //template<typename TObserver> void Connect(MessageType messageType, IObserver& observer, void (TObserver::*ptrToMemberHandler)(IObservable& from, const IMessage& message)) + void Connect(MessageType messageType, IObserver& observer, boost::function<void (IObservable& from, const IMessage& message)> f) + { + callables_[messageType] = std::set<CallableObserver*>(); + CallableObserver* callable = new CallableObserver(); + callable->observer = &observer; + callable->f = f; + callables_[messageType].insert(callable); + } + }; + + + class MyObservable : public IObservable + { + public: + MyObservable(MessageBroker& broker) + : IObservable(broker) + {} + }; + + class MyObserver : public IObserver + { + public: + MyObserver(MessageBroker& broker) + : IObserver(broker) + {} + virtual void HandleMessage(IObservable& from, const IMessage& message) {} + void HandleSpecificMessage(IObservable& from, const IMessage& message) + { + testCounter++; + } + + }; + +} + +//#define STONE_CONNECT(observabe, messageType, observerPtr, observerMemberFnPtr) + +TEST(MessageBroker2, Test1) +{ + MessageBroker broker; + MyObservable observable(broker); + MyObserver observer(broker); + + + observable.Connect(MessageType_Test1, observer, boost::bind(&MyObserver::HandleSpecificMessage, &observer, _1, _2)); + //STONE_CONNECT(observable, MessageType_Test1, observer, &MyObserver::HandleSpecificMessage) + observable.EmitMessage(IMessage(MessageType_Test1)); + + ASSERT_EQ(1, testCounter); +} + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/TestMessageBroker2_promise_and_connect_ok.cpp Fri Sep 14 16:44:01 2018 +0200 @@ -0,0 +1,520 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "gtest/gtest.h" + +#include <boost/noncopyable.hpp> +#include <boost/function.hpp> +#include <boost/bind.hpp> + +#include <string> +#include <map> +#include <set> + +int testCounter = 0; +namespace { + + enum MessageType + { + MessageType_Test1, + MessageType_Test2, + + MessageType_CustomMessage, + MessageType_LastGenericStoneMessage + }; + + struct IMessage : public boost::noncopyable + { + MessageType messageType_; + public: + IMessage(const MessageType& messageType) + : messageType_(messageType) + {} + virtual ~IMessage() {} + + virtual int GetType() const {return messageType_;} + }; + + + struct ICustomMessage : public IMessage + { + int customMessageType_; + public: + ICustomMessage(int customMessageType) + : IMessage(MessageType_CustomMessage), + customMessageType_(customMessageType) + {} + virtual ~ICustomMessage() {} + + virtual int GetType() const {return customMessageType_;} + }; + + + class IObserver; + class IObservable; + class IPromiseTarget; + class IPromiseSource; + class Promise; + + /* + * This is a central message broker. It keeps track of all observers and knows + * when an observer is deleted. + * This way, it can prevent an observable to send a message to a delete observer. + * It does the same book-keeping for the IPromiseTarget and IPromiseSource + */ + class MessageBroker : public boost::noncopyable + { + + std::set<IObserver*> activeObservers_; // the list of observers that are currently alive (that have not been deleted) + std::set<IPromiseTarget*> activePromiseTargets_; + std::set<IPromiseSource*> activePromiseSources_; + + public: + + void Register(IObserver& observer) + { + activeObservers_.insert(&observer); + } + + void Unregister(IObserver& observer) + { + activeObservers_.erase(&observer); + } + + void Register(IPromiseTarget& target) + { + activePromiseTargets_.insert(&target); + } + + void Unregister(IPromiseTarget& target) + { + activePromiseTargets_.erase(&target); + } + + void Register(IPromiseSource& source) + { + activePromiseSources_.insert(&source); + } + + void Unregister(IPromiseSource& source) + { + activePromiseSources_.erase(&source); + } + + void EmitMessage(IObservable& from, std::set<IObserver*> observers, const IMessage& message); + + bool IsActive(IPromiseTarget* target) + { + return activePromiseTargets_.find(target) != activePromiseTargets_.end(); + } + + bool IsActive(IPromiseSource* source) + { + return activePromiseSources_.find(source) != activePromiseSources_.end(); + } + + bool IsActive(IObserver* observer) + { + return activeObservers_.find(observer) != activeObservers_.end(); + } + }; + + struct IPromiseArgs + { +public: + virtual ~IPromiseArgs() {} + }; + + class EmptyPromiseArguments : public IPromiseArgs + { + + }; + + class Promise : public boost::noncopyable + { + protected: + MessageBroker& broker_; + + IPromiseTarget* successTarget_; + boost::function<void (const IPromiseArgs& message)> successCallable_; + + IPromiseTarget* failureTarget_; + boost::function<void (const IPromiseArgs& message)> failureCallable_; + + public: + Promise(MessageBroker& broker) + : broker_(broker), + successTarget_(NULL), + failureTarget_(NULL) + { + } + + void Success(const IPromiseArgs& message) + { + // check the target is still alive in the broker + if (broker_.IsActive(successTarget_)) + { + successCallable_(message); + } + } + + void Failure(const IPromiseArgs& message) + { + // check the target is still alive in the broker + if (broker_.IsActive(failureTarget_)) + { + failureCallable_(message); + } + } + + Promise& Then(IPromiseTarget* target, boost::function<void (const IPromiseArgs& message)> f) + { + if (successTarget_ != NULL) + { + // TODO: throw throw new "Promise may only have a single success target" + } + successTarget_ = target; + successCallable_ = f; + return *this; + } + + Promise& Else(IPromiseTarget* target, boost::function<void (const IPromiseArgs& message)> f) + { + if (failureTarget_ != NULL) + { + // TODO: throw throw new "Promise may only have a single failure target" + } + failureTarget_ = target; + failureCallable_ = f; + return *this; + } + + }; + + class IObserver : public boost::noncopyable + { + protected: + MessageBroker& broker_; + + public: + IObserver(MessageBroker& broker) + : broker_(broker) + { + broker_.Register(*this); + } + + virtual ~IObserver() + { + broker_.Unregister(*this); + } + + }; + + class IPromiseTarget : public boost::noncopyable + { + protected: + MessageBroker& broker_; + + public: + IPromiseTarget(MessageBroker& broker) + : broker_(broker) + { + broker_.Register(*this); + } + + virtual ~IPromiseTarget() + { + broker_.Unregister(*this); + } + }; + + class IPromiseSource : public boost::noncopyable + { + protected: + MessageBroker& broker_; + + public: + IPromiseSource(MessageBroker& broker) + : broker_(broker) + { + broker_.Register(*this); + } + + virtual ~IPromiseSource() + { + broker_.Unregister(*this); + } + }; + + + struct CallableObserver + { + IObserver* observer; + boost::function<void (IObservable& from, const IMessage& message)> f; + }; + + class IObservable : public boost::noncopyable + { + protected: + MessageBroker& broker_; + + std::set<IObserver*> observers_; + + std::map<int, std::set<CallableObserver*> > callables_; + public: + + IObservable(MessageBroker& broker) + : broker_(broker) + { + } + virtual ~IObservable() + { + } + + void EmitMessage(const IMessage& message) + { + //broker_.EmitMessage(*this, observers_, message); + int messageType = message.GetType(); + if (callables_.find(messageType) != callables_.end()) + { + for (std::set<CallableObserver*>::iterator observer = callables_[messageType].begin(); observer != callables_[messageType].end(); observer++) + { + CallableObserver* callable = *observer; + if (broker_.IsActive(callable->observer)) + { + callable->f(*this, message); + } + } + } + + } + + void RegisterObserver(IObserver& observer) + { + observers_.insert(&observer); + } + + void UnregisterObserver(IObserver& observer) + { + observers_.erase(&observer); + } + + //template<typename TObserver> void Connect(MessageType messageType, IObserver& observer, void (TObserver::*ptrToMemberHandler)(IObservable& from, const IMessage& message)) + void Connect(int messageType, IObserver& observer, boost::function<void (IObservable& from, const IMessage& message)> f) + { + callables_[messageType] = std::set<CallableObserver*>(); + CallableObserver* callable = new CallableObserver(); + callable->observer = &observer; + callable->f = f; + callables_[messageType].insert(callable); + } + }; + + + enum CustomMessageType + { + CustomMessageType_First = MessageType_LastGenericStoneMessage + 1, + + CustomMessageType_Completed + }; + + class MyObservable : public IObservable + { + public: + struct MyCustomMessage: public ICustomMessage + { + int payload_; + MyCustomMessage(int payload) + : ICustomMessage(CustomMessageType_Completed), + payload_(payload) + {} + }; + + MyObservable(MessageBroker& broker) + : IObservable(broker) + {} + + }; + + class MyObserver : public IObserver + { + public: + MyObserver(MessageBroker& broker) + : IObserver(broker) + {} + void HandleCompletedMessage(IObservable& from, const IMessage& message) + { + const MyObservable::MyCustomMessage& msg = dynamic_cast<const MyObservable::MyCustomMessage&>(message); + testCounter += msg.payload_; + } + + }; + + + class MyPromiseSource : public IPromiseSource + { + Promise* currentPromise_; + public: + struct MyPromiseArgs : public IPromiseArgs + { + int increment; + }; + + MyPromiseSource(MessageBroker& broker) + : IPromiseSource(broker), + currentPromise_(NULL) + {} + + Promise& StartSomethingAsync() + { + currentPromise_ = new Promise(broker_); + return *currentPromise_; + } + + void CompleteSomethingAsyncWithSuccess() + { + currentPromise_->Success(EmptyPromiseArguments()); + delete currentPromise_; + } + + void CompleteSomethingAsyncWithFailure() + { + currentPromise_->Failure(EmptyPromiseArguments()); + delete currentPromise_; + } + }; + + + class MyPromiseTarget : public IPromiseTarget + { + public: + MyPromiseTarget(MessageBroker& broker) + : IPromiseTarget(broker) + {} + + void IncrementCounter(const IPromiseArgs& args) + { + testCounter++; + } + + void DecrementCounter(const IPromiseArgs& args) + { + testCounter--; + } + }; +} + +#define CONNECT_MESSAGES(observablePtr, messageType, observerPtr, observerFnPtr) (observablePtr)->Connect(messageType, *(observerPtr), boost::bind(observerFnPtr, observerPtr, _1, _2)) +#define PTHEN(targetPtr, targetFnPtr) Then(targetPtr, boost::bind(targetFnPtr, targetPtr, _1)) +#define PELSE(targetPtr, targetFnPtr) Else(targetPtr, boost::bind(targetFnPtr, targetPtr, _1)) + + +TEST(MessageBroker2, TestPermanentConnectionSimpleUseCase) +{ + MessageBroker broker; + MyObservable observable(broker); + MyObserver observer(broker); + + // create a permanent connection between an observable and an observer + CONNECT_MESSAGES(&observable, CustomMessageType_Completed, &observer, &MyObserver::HandleCompletedMessage); + + testCounter = 0; + observable.EmitMessage(MyObservable::MyCustomMessage(12)); + ASSERT_EQ(12, testCounter); + + // the connection is permanent; if we emit the same message again, the observer will be notified again + testCounter = 0; + observable.EmitMessage(MyObservable::MyCustomMessage(20)); + ASSERT_EQ(20, testCounter); +} + +TEST(MessageBroker2, TestPermanentConnectionDeleteObserver) +{ + MessageBroker broker; + MyObservable observable(broker); + MyObserver* observer = new MyObserver(broker); + + // create a permanent connection between an observable and an observer + CONNECT_MESSAGES(&observable, CustomMessageType_Completed, observer, &MyObserver::HandleCompletedMessage); + + testCounter = 0; + observable.EmitMessage(MyObservable::MyCustomMessage(12)); + ASSERT_EQ(12, testCounter); + + // delete the observer and check that the callback is not called anymore + delete observer; + + // the connection is permanent; if we emit the same message again, the observer will be notified again + testCounter = 0; + observable.EmitMessage(MyObservable::MyCustomMessage(20)); + ASSERT_EQ(0, testCounter); +} + + +TEST(MessageBroker2, TestPromiseSuccessFailure) +{ + MessageBroker broker; + MyPromiseSource source(broker); + MyPromiseTarget target(broker); + + // test a successful promise + source.StartSomethingAsync() + .PTHEN(&target, &MyPromiseTarget::IncrementCounter) + .PELSE(&target, &MyPromiseTarget::DecrementCounter); + + testCounter = 0; + source.CompleteSomethingAsyncWithSuccess(); + ASSERT_EQ(1, testCounter); + + // test a failing promise + source.StartSomethingAsync() + .PTHEN(&target, &MyPromiseTarget::IncrementCounter) + .PELSE(&target, &MyPromiseTarget::DecrementCounter); + + testCounter = 0; + source.CompleteSomethingAsyncWithFailure(); + ASSERT_EQ(-1, testCounter); +} + +//TEST(MessageBroker2, TestPromiseDeleteTarget) +//{ +// MessageBroker broker; +// MyPromiseSource source(broker); +// MyPromiseTarget target(broker); + +// // test a successful promise +// source.StartSomethingAsync() +// .PTHEN(&target, &MyPromiseTarget::IncrementCounter) +// .PELSE(&target, &MyPromiseTarget::DecrementCounter); + +// testCounter = 0; +// source.CompleteSomethingAsyncWithSuccess(); +// ASSERT_EQ(1, testCounter); + +// // test a failing promise +// source.StartSomethingAsync() +// .PTHEN(&target, &MyPromiseTarget::IncrementCounter) +// .PELSE(&target, &MyPromiseTarget::DecrementCounter); + +// testCounter = 0; +// source.CompleteSomethingAsyncWithFailure(); +// ASSERT_EQ(-1, testCounter); +//}