Mercurial > hg > orthanc-stone
changeset 829:3a984741686f
Merge
author | Benjamin Golinvaux <bgo@osimis.io> |
---|---|
date | Wed, 29 May 2019 16:15:23 +0200 |
parents | 28f99af358fa (current diff) 9a6c7a5dcb76 (diff) |
children | 171a486a0373 |
files | Framework/Oracle/ThreadedOracle.h |
diffstat | 8 files changed, 673 insertions(+), 406 deletions(-) [+] |
line wrap: on
line diff
--- a/Framework/Oracle/IOracle.h Wed May 29 16:15:04 2019 +0200 +++ b/Framework/Oracle/IOracle.h Wed May 29 16:15:23 2019 +0200 @@ -21,6 +21,7 @@ #pragma once +#include "../Messages/IObserver.h" #include "IOracleCommand.h" namespace OrthancStone @@ -32,10 +33,6 @@ { } - virtual void Start() = 0; - - virtual void Stop() = 0; - virtual void Schedule(const IObserver& receiver, IOracleCommand* command) = 0; // Takes ownership };
--- a/Framework/Oracle/SleepOracleCommand.h Wed May 29 16:15:04 2019 +0200 +++ b/Framework/Oracle/SleepOracleCommand.h Wed May 29 16:15:23 2019 +0200 @@ -35,7 +35,7 @@ ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, TimeoutMessage, SleepOracleCommand); SleepOracleCommand(unsigned int milliseconds) : - milliseconds_(milliseconds) + milliseconds_(milliseconds) { }
--- a/Framework/Oracle/ThreadedOracle.h Wed May 29 16:15:04 2019 +0200 +++ b/Framework/Oracle/ThreadedOracle.h Wed May 29 16:15:23 2019 +0200 @@ -81,9 +81,9 @@ void SetSleepingTimeResolution(unsigned int milliseconds); - virtual void Start(); + void Start(); - virtual void Stop() + void Stop() { StopInternal(); }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/WebAssemblyOracle.cpp Wed May 29 16:15:23 2019 +0200 @@ -0,0 +1,466 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 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 "WebAssemblyOracle.h" + +#include "SleepOracleCommand.h" + +#include <Core/OrthancException.h> +#include <Core/Toolbox.h> + +#include <emscripten.h> +#include <emscripten/html5.h> +#include <emscripten/fetch.h> + + +namespace OrthancStone +{ + class WebAssemblyOracle::TimeoutContext + { + private: + WebAssemblyOracle& oracle_; + const IObserver& receiver_; + std::auto_ptr<SleepOracleCommand> command_; + + public: + TimeoutContext(WebAssemblyOracle& oracle, + const IObserver& receiver, + IOracleCommand* command) : + oracle_(oracle), + receiver_(receiver) + { + if (command == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + else + { + command_.reset(dynamic_cast<SleepOracleCommand*>(command)); + } + } + + void EmitMessage() + { + SleepOracleCommand::TimeoutMessage message(*command_); + oracle_.EmitMessage(receiver_, message); + } + + static void Callback(void *userData) + { + std::auto_ptr<TimeoutContext> context(reinterpret_cast<TimeoutContext*>(userData)); + context->EmitMessage(); + } + }; + + + class WebAssemblyOracle::Emitter : public IMessageEmitter + { + private: + WebAssemblyOracle& oracle_; + + public: + Emitter(WebAssemblyOracle& oracle) : + oracle_(oracle) + { + } + + virtual void EmitMessage(const IObserver& receiver, + const IMessage& message) + { + oracle_.EmitMessage(receiver, message); + } + }; + + + class WebAssemblyOracle::FetchContext : public boost::noncopyable + { + private: + Emitter emitter_; + const IObserver& receiver_; + std::auto_ptr<IOracleCommand> command_; + std::string expectedContentType_; + + public: + FetchContext(WebAssemblyOracle& oracle, + const IObserver& receiver, + IOracleCommand* command, + const std::string& expectedContentType) : + emitter_(oracle), + receiver_(receiver), + command_(command), + expectedContentType_(expectedContentType) + { + if (command == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + const std::string& GetExpectedContentType() const + { + return expectedContentType_; + } + + void EmitMessage(const IMessage& message) + { + emitter_.EmitMessage(receiver_, message); + } + + IMessageEmitter& GetEmitter() + { + return emitter_; + } + + const IObserver& GetReceiver() const + { + return receiver_; + } + + IOracleCommand& GetCommand() const + { + return *command_; + } + + template <typename T> + const T& GetTypedCommand() const + { + return dynamic_cast<T&>(*command_); + } + + static void SuccessCallback(emscripten_fetch_t *fetch) + { + /** + * Firstly, make a local copy of the fetched information, and + * free data associated with the fetch. + **/ + + std::auto_ptr<FetchContext> context(reinterpret_cast<FetchContext*>(fetch->userData)); + + std::string answer; + if (fetch->numBytes > 0) + { + answer.assign(fetch->data, fetch->numBytes); + } + + /** + * TODO - HACK - As of emscripten-1.38.31, the fetch API does + * not contain a way to retrieve the HTTP headers of the + * answer. We make the assumption that the "Content-Type" header + * of the response is the same as the "Accept" header of the + * query. This should be fixed in future versions of emscripten. + * https://github.com/emscripten-core/emscripten/pull/8486 + **/ + + HttpHeaders headers; + if (!context->GetExpectedContentType().empty()) + { + headers["Content-Type"] = context->GetExpectedContentType(); + } + + + emscripten_fetch_close(fetch); + + + /** + * Secondly, use the retrieved data. + **/ + + try + { + if (context.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + else + { + switch (context->GetCommand().GetType()) + { + case IOracleCommand::Type_OrthancRestApi: + { + OrthancRestApiCommand::SuccessMessage message + (context->GetTypedCommand<OrthancRestApiCommand>(), headers, answer); + context->EmitMessage(message); + break; + } + + case IOracleCommand::Type_GetOrthancImage: + { + context->GetTypedCommand<GetOrthancImageCommand>().ProcessHttpAnswer + (context->GetEmitter(), context->GetReceiver(), answer, headers); + break; + } + + case IOracleCommand::Type_GetOrthancWebViewerJpeg: + { + context->GetTypedCommand<GetOrthancWebViewerJpegCommand>().ProcessHttpAnswer + (context->GetEmitter(), context->GetReceiver(), answer); + break; + } + + default: + LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle: " + << context->GetCommand().GetType(); + } + } + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Error while processing a fetch answer in the oracle: " << e.What(); + } + } + + static void FailureCallback(emscripten_fetch_t *fetch) + { + std::auto_ptr<FetchContext> context(reinterpret_cast<FetchContext*>(fetch->userData)); + + LOG(ERROR) << "Fetching " << fetch->url << " failed, HTTP failure status code: " << fetch->status; + + /** + * TODO - The following code leads to an infinite recursion, at + * least with Firefox running on incognito mode => WHY? + **/ + //emscripten_fetch_close(fetch); // Also free data on failure. + } + }; + + + + class WebAssemblyOracle::FetchCommand : public boost::noncopyable + { + private: + WebAssemblyOracle& oracle_; + const IObserver& receiver_; + std::auto_ptr<IOracleCommand> command_; + Orthanc::HttpMethod method_; + std::string uri_; + std::string body_; + HttpHeaders headers_; + unsigned int timeout_; + std::string expectedContentType_; + + public: + FetchCommand(WebAssemblyOracle& oracle, + const IObserver& receiver, + IOracleCommand* command) : + oracle_(oracle), + receiver_(receiver), + command_(command), + method_(Orthanc::HttpMethod_Get), + timeout_(0) + { + if (command == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + void SetMethod(Orthanc::HttpMethod method) + { + method_ = method; + } + + void SetUri(const std::string& uri) + { + uri_ = uri; + } + + void SetBody(std::string& body /* will be swapped */) + { + body_.swap(body); + } + + void SetHttpHeaders(const HttpHeaders& headers) + { + headers_ = headers; + } + + void SetTimeout(unsigned int timeout) + { + timeout_ = timeout; + } + + void Execute() + { + if (command_.get() == NULL) + { + // Cannot call Execute() twice + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + emscripten_fetch_attr_t attr; + emscripten_fetch_attr_init(&attr); + + const char* method; + + switch (method_) + { + case Orthanc::HttpMethod_Get: + method = "GET"; + break; + + case Orthanc::HttpMethod_Post: + method = "POST"; + break; + + case Orthanc::HttpMethod_Delete: + method = "DELETE"; + break; + + case Orthanc::HttpMethod_Put: + method = "PUT"; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + strcpy(attr.requestMethod, method); + + attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; + attr.onsuccess = FetchContext::SuccessCallback; + attr.onerror = FetchContext::FailureCallback; + attr.timeoutMSecs = timeout_ * 1000; + + std::vector<const char*> headers; + headers.reserve(2 * headers_.size() + 1); + + std::string expectedContentType; + + for (HttpHeaders::const_iterator it = headers_.begin(); it != headers_.end(); ++it) + { + std::string key; + Orthanc::Toolbox::ToLowerCase(key, it->first); + + if (key == "accept") + { + expectedContentType = it->second; + } + + if (key != "accept-encoding") // Web browsers forbid the modification of this HTTP header + { + headers.push_back(it->first.c_str()); + headers.push_back(it->second.c_str()); + } + } + + headers.push_back(NULL); // Termination of the array of HTTP headers + + attr.requestHeaders = &headers[0]; + + if (!body_.empty()) + { + attr.requestDataSize = body_.size(); + attr.requestData = body_.c_str(); + } + + // Must be the last call to prevent memory leak on error + attr.userData = new FetchContext(oracle_, receiver_, command_.release(), expectedContentType); + emscripten_fetch(&attr, uri_.c_str()); + } + }; + + + void WebAssemblyOracle::Execute(const IObserver& receiver, + OrthancRestApiCommand* command) + { + FetchCommand fetch(*this, receiver, command); + + fetch.SetMethod(command->GetMethod()); + fetch.SetUri(command->GetUri()); + fetch.SetHttpHeaders(command->GetHttpHeaders()); + fetch.SetTimeout(command->GetTimeout()); + + if (command->GetMethod() == Orthanc::HttpMethod_Put || + command->GetMethod() == Orthanc::HttpMethod_Put) + { + std::string body; + command->SwapBody(body); + fetch.SetBody(body); + } + + fetch.Execute(); + } + + + void WebAssemblyOracle::Execute(const IObserver& receiver, + GetOrthancImageCommand* command) + { + FetchCommand fetch(*this, receiver, command); + + fetch.SetUri(command->GetUri()); + fetch.SetHttpHeaders(command->GetHttpHeaders()); + fetch.SetTimeout(command->GetTimeout()); + + fetch.Execute(); + } + + + void WebAssemblyOracle::Execute(const IObserver& receiver, + GetOrthancWebViewerJpegCommand* command) + { + FetchCommand fetch(*this, receiver, command); + + fetch.SetUri(command->GetUri()); + fetch.SetHttpHeaders(command->GetHttpHeaders()); + fetch.SetTimeout(command->GetTimeout()); + + fetch.Execute(); + } + + + + void WebAssemblyOracle::Schedule(const IObserver& receiver, + IOracleCommand* command) + { + std::auto_ptr<IOracleCommand> protection(command); + + if (command == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + switch (command->GetType()) + { + case IOracleCommand::Type_OrthancRestApi: + Execute(receiver, dynamic_cast<OrthancRestApiCommand*>(protection.release())); + break; + + case IOracleCommand::Type_GetOrthancImage: + Execute(receiver, dynamic_cast<GetOrthancImageCommand*>(protection.release())); + break; + + case IOracleCommand::Type_GetOrthancWebViewerJpeg: + Execute(receiver, dynamic_cast<GetOrthancWebViewerJpegCommand*>(protection.release())); + break; + + case IOracleCommand::Type_Sleep: + { + unsigned int timeoutMS = dynamic_cast<SleepOracleCommand*>(command)->GetDelay(); + emscripten_set_timeout(TimeoutContext::Callback, timeoutMS, + new TimeoutContext(*this, receiver, protection.release())); + break; + } + + default: + LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle: " << command->GetType(); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/WebAssemblyOracle.h Wed May 29 16:15:23 2019 +0200 @@ -0,0 +1,71 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 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 + +#if !defined(ORTHANC_ENABLE_WASM) +# error The macro ORTHANC_ENABLE_WASM must be defined +#endif + +#if ORTHANC_ENABLE_WASM != 1 +# error This file can only compiled for WebAssembly +#endif + +#include "../Messages/IObservable.h" +#include "GetOrthancImageCommand.h" +#include "GetOrthancWebViewerJpegCommand.h" +#include "IOracle.h" +#include "OrthancRestApiCommand.h" + + +namespace OrthancStone +{ + class WebAssemblyOracle : + public IOracle, + public IObservable + { + private: + typedef std::map<std::string, std::string> HttpHeaders; + + class TimeoutContext; + class Emitter; + class FetchContext; + class FetchCommand; + + void Execute(const IObserver& receiver, + OrthancRestApiCommand* command); + + void Execute(const IObserver& receiver, + GetOrthancImageCommand* command); + + void Execute(const IObserver& receiver, + GetOrthancWebViewerJpegCommand* command); + + public: + WebAssemblyOracle(MessageBroker& broker) : + IObservable(broker) + { + } + + virtual void Schedule(const IObserver& receiver, + IOracleCommand* command); + }; +}
--- a/Framework/Scene2D/Scene2D.cpp Wed May 29 16:15:04 2019 +0200 +++ b/Framework/Scene2D/Scene2D.cpp Wed May 29 16:15:23 2019 +0200 @@ -102,8 +102,7 @@ void Scene2D::SetLayer(int depth, ISceneLayer* layer) // Takes ownership { - LOG(INFO) << "SetLayer(" << depth << ", " << - reinterpret_cast<intptr_t>(layer) << ")"; + LOG(TRACE) << "SetLayer(" << depth << ", " << reinterpret_cast<intptr_t>(layer) << ")"; std::auto_ptr<Item> item(new Item(layer, layerCounter_++)); if (layer == NULL)
--- a/Resources/CMake/OrthancStoneConfiguration.cmake Wed May 29 16:15:04 2019 +0200 +++ b/Resources/CMake/OrthancStoneConfiguration.cmake Wed May 29 16:15:23 2019 +0200 @@ -373,6 +373,13 @@ endif() +if (ENABLE_WASM) + list(APPEND ORTHANC_STONE_SOURCES + ${ORTHANC_STONE_ROOT}/Framework/Oracle/WebAssemblyOracle.cpp + ) +endif() + + list(APPEND ORTHANC_STONE_SOURCES #${ORTHANC_STONE_ROOT}/Framework/Layers/SeriesFrameRendererFactory.cpp #${ORTHANC_STONE_ROOT}/Framework/Layers/SingleFrameRendererFactory.cpp
--- a/Samples/WebAssembly/BasicMPR.cpp Wed May 29 16:15:04 2019 +0200 +++ b/Samples/WebAssembly/BasicMPR.cpp Wed May 29 16:15:23 2019 +0200 @@ -26,6 +26,8 @@ #include "../../Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h" #include "../../Framework/OpenGL/WebAssemblyOpenGLContext.h" +#include "../../Framework/Oracle/SleepOracleCommand.h" +#include "../../Framework/Oracle/WebAssemblyOracle.h" #include "../../Framework/Scene2D/GrayscaleStyleConfigurator.h" #include "../../Framework/Scene2D/OpenGLCompositor.h" #include "../../Framework/Scene2D/PanSceneTracker.h" @@ -91,6 +93,11 @@ compositor_.Refresh(); } + void FitContent() + { + GetScene().FitContent(context_.GetCanvasWidth(), context_.GetCanvasHeight()); + } + const std::string& GetCanvasIdentifier() const { return context_.GetCanvasIdentifier(); @@ -303,6 +310,10 @@ { planes_[z] = geometry.GetProjectionSlice(projection_, z); } + + Refresh(); + + viewport_.FitContent(); } public: @@ -350,414 +361,36 @@ currentPlane_ < planes_.size()) { source_->Update(planes_[currentPlane_]); - } - } - }; - - - - - class WebAssemblyOracle : - public IOracle, - public IObservable - { - private: - typedef std::map<std::string, std::string> HttpHeaders; - - class FetchContext : public boost::noncopyable - { - private: - class Emitter : public IMessageEmitter - { - private: - WebAssemblyOracle& oracle_; - - public: - Emitter(WebAssemblyOracle& oracle) : - oracle_(oracle) - { - } - - virtual void EmitMessage(const IObserver& receiver, - const IMessage& message) - { - oracle_.EmitMessage(receiver, message); - } - }; - - Emitter emitter_; - const IObserver& receiver_; - std::auto_ptr<IOracleCommand> command_; - std::string expectedContentType_; - - public: - FetchContext(WebAssemblyOracle& oracle, - const IObserver& receiver, - IOracleCommand* command, - const std::string& expectedContentType) : - emitter_(oracle), - receiver_(receiver), - command_(command), - expectedContentType_(expectedContentType) - { - if (command == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - } - - const std::string& GetExpectedContentType() const - { - return expectedContentType_; - } - - void EmitMessage(const IMessage& message) - { - emitter_.EmitMessage(receiver_, message); - } - - IMessageEmitter& GetEmitter() - { - return emitter_; - } - - const IObserver& GetReceiver() const - { - return receiver_; - } - - IOracleCommand& GetCommand() const - { - return *command_; - } - - template <typename T> - const T& GetTypedCommand() const - { - return dynamic_cast<T&>(*command_); - } - }; - - static void FetchSucceeded(emscripten_fetch_t *fetch) - { - /** - * Firstly, make a local copy of the fetched information, and - * free data associated with the fetch. - **/ - - std::auto_ptr<FetchContext> context(reinterpret_cast<FetchContext*>(fetch->userData)); - - std::string answer; - if (fetch->numBytes > 0) - { - answer.assign(fetch->data, fetch->numBytes); - } - - /** - * TODO - HACK - As of emscripten-1.38.31, the fetch API does - * not contain a way to retrieve the HTTP headers of the - * answer. We make the assumption that the "Content-Type" header - * of the response is the same as the "Accept" header of the - * query. This should be fixed in future versions of emscripten. - * https://github.com/emscripten-core/emscripten/pull/8486 - **/ - - HttpHeaders headers; - if (!context->GetExpectedContentType().empty()) - { - headers["Content-Type"] = context->GetExpectedContentType(); - } - - - emscripten_fetch_close(fetch); - - - /** - * Secondly, use the retrieved data. - **/ - - try - { - if (context.get() == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - else - { - switch (context->GetCommand().GetType()) - { - case IOracleCommand::Type_OrthancRestApi: - { - OrthancRestApiCommand::SuccessMessage message - (context->GetTypedCommand<OrthancRestApiCommand>(), headers, answer); - context->EmitMessage(message); - break; - } - - case IOracleCommand::Type_GetOrthancImage: - { - context->GetTypedCommand<GetOrthancImageCommand>().ProcessHttpAnswer - (context->GetEmitter(), context->GetReceiver(), answer, headers); - break; - } - - case IOracleCommand::Type_GetOrthancWebViewerJpeg: - { - context->GetTypedCommand<GetOrthancWebViewerJpegCommand>().ProcessHttpAnswer - (context->GetEmitter(), context->GetReceiver(), answer); - break; - } - - default: - LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle: " - << context->GetCommand().GetType(); - } - } - } - catch (Orthanc::OrthancException& e) - { - LOG(ERROR) << "Error while processing a fetch answer in the oracle: " << e.What(); + viewport_.Refresh(); } } - static void FetchFailed(emscripten_fetch_t *fetch) - { - std::auto_ptr<FetchContext> context(reinterpret_cast<FetchContext*>(fetch->userData)); - - LOG(ERROR) << "Fetching " << fetch->url << " failed, HTTP failure status code: " << fetch->status; - - /** - * TODO - The following code leads to an infinite recursion, at - * least with Firefox running on incognito mode => WHY? - **/ - //emscripten_fetch_close(fetch); // Also free data on failure. - } - - - class FetchCommand : public boost::noncopyable + void Scroll(int delta) { - private: - WebAssemblyOracle& oracle_; - const IObserver& receiver_; - std::auto_ptr<IOracleCommand> command_; - Orthanc::HttpMethod method_; - std::string uri_; - std::string body_; - HttpHeaders headers_; - unsigned int timeout_; - std::string expectedContentType_; - - public: - FetchCommand(WebAssemblyOracle& oracle, - const IObserver& receiver, - IOracleCommand* command) : - oracle_(oracle), - receiver_(receiver), - command_(command), - method_(Orthanc::HttpMethod_Get), - timeout_(0) - { - if (command == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - } - - void SetMethod(Orthanc::HttpMethod method) - { - method_ = method; - } - - void SetUri(const std::string& uri) + if (!planes_.empty()) { - uri_ = uri; - } - - void SetBody(std::string& body /* will be swapped */) - { - body_.swap(body); - } - - void SetHttpHeaders(const HttpHeaders& headers) - { - headers_ = headers; - } - - void SetTimeout(unsigned int timeout) - { - timeout_ = timeout; - } - - void Execute() - { - if (command_.get() == NULL) - { - // Cannot call Execute() twice - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } + int tmp = static_cast<int>(currentPlane_) + delta; + unsigned int next; - emscripten_fetch_attr_t attr; - emscripten_fetch_attr_init(&attr); - - const char* method; - - switch (method_) + if (tmp < 0) + { + next = 0; + } + else if (tmp >= static_cast<int>(planes_.size())) { - case Orthanc::HttpMethod_Get: - method = "GET"; - break; - - case Orthanc::HttpMethod_Post: - method = "POST"; - break; - - case Orthanc::HttpMethod_Delete: - method = "DELETE"; - break; - - case Orthanc::HttpMethod_Put: - method = "PUT"; - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + next = planes_.size() - 1; + } + else + { + next = static_cast<size_t>(tmp); } - strcpy(attr.requestMethod, method); - - attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; - attr.onsuccess = FetchSucceeded; - attr.onerror = FetchFailed; - attr.timeoutMSecs = timeout_ * 1000; - - std::vector<const char*> headers; - headers.reserve(2 * headers_.size() + 1); - - std::string expectedContentType; - - for (HttpHeaders::const_iterator it = headers_.begin(); it != headers_.end(); ++it) + if (next != currentPlane_) { - std::string key; - Orthanc::Toolbox::ToLowerCase(key, it->first); - - if (key == "accept") - { - expectedContentType = it->second; - } - - if (key != "accept-encoding") // Web browsers forbid the modification of this HTTP header - { - headers.push_back(it->first.c_str()); - headers.push_back(it->second.c_str()); - } + currentPlane_ = next; + Refresh(); } - - headers.push_back(NULL); // Termination of the array of HTTP headers - - attr.requestHeaders = &headers[0]; - - if (!body_.empty()) - { - attr.requestDataSize = body_.size(); - attr.requestData = body_.c_str(); - } - - // Must be the last call to prevent memory leak on error - attr.userData = new FetchContext(oracle_, receiver_, command_.release(), expectedContentType); - emscripten_fetch(&attr, uri_.c_str()); - } - }; - - - void Execute(const IObserver& receiver, - OrthancRestApiCommand* command) - { - FetchCommand fetch(*this, receiver, command); - - fetch.SetMethod(command->GetMethod()); - fetch.SetUri(command->GetUri()); - fetch.SetHttpHeaders(command->GetHttpHeaders()); - fetch.SetTimeout(command->GetTimeout()); - - if (command->GetMethod() == Orthanc::HttpMethod_Put || - command->GetMethod() == Orthanc::HttpMethod_Put) - { - std::string body; - command->SwapBody(body); - fetch.SetBody(body); } - - fetch.Execute(); - } - - - void Execute(const IObserver& receiver, - GetOrthancImageCommand* command) - { - FetchCommand fetch(*this, receiver, command); - - fetch.SetUri(command->GetUri()); - fetch.SetHttpHeaders(command->GetHttpHeaders()); - fetch.SetTimeout(command->GetTimeout()); - - fetch.Execute(); - } - - - void Execute(const IObserver& receiver, - GetOrthancWebViewerJpegCommand* command) - { - FetchCommand fetch(*this, receiver, command); - - fetch.SetUri(command->GetUri()); - fetch.SetHttpHeaders(command->GetHttpHeaders()); - fetch.SetTimeout(command->GetTimeout()); - - fetch.Execute(); - } - - - public: - WebAssemblyOracle(MessageBroker& broker) : - IObservable(broker) - { - } - - virtual void Schedule(const IObserver& receiver, - IOracleCommand* command) - { - std::auto_ptr<IOracleCommand> protection(command); - - if (command == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - - switch (command->GetType()) - { - case IOracleCommand::Type_OrthancRestApi: - Execute(receiver, dynamic_cast<OrthancRestApiCommand*>(protection.release())); - break; - - case IOracleCommand::Type_GetOrthancImage: - Execute(receiver, dynamic_cast<GetOrthancImageCommand*>(protection.release())); - break; - - case IOracleCommand::Type_GetOrthancWebViewerJpeg: - Execute(receiver, dynamic_cast<GetOrthancWebViewerJpegCommand*>(protection.release())); - break; - - default: - LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle: " << command->GetType(); - } - } - - virtual void Start() - { - } - - virtual void Stop() - { } }; } @@ -836,6 +469,94 @@ } +static bool ctrlDown_ = false; + + +EM_BOOL OnMouseWheel(int eventType, + const EmscriptenWheelEvent *wheelEvent, + void *userData) +{ + try + { + if (userData != NULL) + { + int delta = 0; + + if (wheelEvent->deltaY < 0) + { + delta = -1; + } + + if (wheelEvent->deltaY > 0) + { + delta = 1; + } + + if (ctrlDown_) + { + delta *= 10; + } + + reinterpret_cast<OrthancStone::VolumeSlicerViewport*>(userData)->Scroll(delta); + } + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Exception in the wheel event: " << e.What(); + } + + return true; +} + + +EM_BOOL OnKey(int eventType, + const EmscriptenKeyboardEvent *keyEvent, + void *userData) +{ + ctrlDown_ = keyEvent->ctrlKey; + return false; +} + + + + +namespace OrthancStone +{ + class TestSleep : public IObserver + { + private: + WebAssemblyOracle& oracle_; + + void Schedule() + { + oracle_.Schedule(*this, new OrthancStone::SleepOracleCommand(2000)); + } + + void Handle(const SleepOracleCommand::TimeoutMessage& message) + { + LOG(INFO) << "TIMEOUT"; + Schedule(); + } + + public: + TestSleep(MessageBroker& broker, + WebAssemblyOracle& oracle) : + IObserver(broker), + oracle_(oracle) + { + oracle.RegisterObserverCallback( + new Callable<TestSleep, SleepOracleCommand::TimeoutMessage> + (*this, &TestSleep::Handle)); + + LOG(INFO) << "STARTING"; + Schedule(); + } + }; + + static TestSleep testSleep(broker_, oracle_); +} + + extern "C" { int main(int argc, char const *argv[]) @@ -866,10 +587,16 @@ viewport3_->UpdateSize(); emscripten_set_resize_callback("#window", NULL, false, OnWindowResize); + + emscripten_set_wheel_callback("mycanvas1", viewport1_.get(), false, OnMouseWheel); + emscripten_set_wheel_callback("mycanvas2", viewport2_.get(), false, OnMouseWheel); + emscripten_set_wheel_callback("mycanvas3", viewport3_.get(), false, OnMouseWheel); + + emscripten_set_keydown_callback("#window", NULL, false, OnKey); + emscripten_set_keyup_callback("#window", NULL, false, OnKey); emscripten_request_animation_frame_loop(OnAnimationFrame, NULL); - oracle_.Start(); loader_->LoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"); } catch (Orthanc::OrthancException& e)