# HG changeset patch # User am@osimis.io # Date 1543515915 -3600 # Node ID 26b90b1107194173ab0719ccbff41673e4f5be88 # Parent b85f635f1eb5f6acb2c8f3dbca8b4fdd989f69fb added DelayedCallExecutor to avoid using sleep() in C++ that consumes 100% CPU once executed in WASM diff -r b85f635f1eb5 -r 26b90b110719 Applications/Generic/NativeStoneApplicationRunner.cpp --- a/Applications/Generic/NativeStoneApplicationRunner.cpp Thu Nov 29 15:11:19 2018 +0100 +++ b/Applications/Generic/NativeStoneApplicationRunner.cpp Thu Nov 29 19:25:15 2018 +0100 @@ -27,6 +27,7 @@ #include "../../Framework/Toolbox/MessagingToolbox.h" #include "../../Platforms/Generic/OracleWebService.h" +#include "../../Platforms/Generic/OracleDelayedCallExecutor.h" #include "NativeStoneApplicationContext.h" #include @@ -188,7 +189,7 @@ NativeStoneApplicationContext context(broker_); { - Oracle oracle(4); // use 4 threads to download content + Oracle oracle(6); // use multiple threads to execute asynchronous tasks like download content oracle.Start(); { @@ -196,6 +197,9 @@ context.SetWebService(webService); context.SetOrthancBaseUrl(webServiceParameters.GetUrl()); + OracleDelayedCallExecutor delayedExecutor(broker_, oracle, context); + context.SetDelayedCallExecutor(delayedExecutor); + application_.Initialize(&context, statusBar, parameters); { diff -r b85f635f1eb5 -r 26b90b110719 Applications/Samples/CMakeLists.txt --- a/Applications/Samples/CMakeLists.txt Thu Nov 29 15:11:19 2018 +0100 +++ b/Applications/Samples/CMakeLists.txt Thu Nov 29 19:25:15 2018 +0100 @@ -23,7 +23,7 @@ set(WASM_MODULE_NAME "StoneFrameworkModule" CACHE STRING "Name of the WebAssembly module") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Applications/Samples/samples-library.js --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmWebService.js --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/default-library.js -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Applications/Samples/samples-library.js --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmWebService.js --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmDelayedCallExecutor.js --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/default-library.js -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") # Handling of memory #set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1") # Resize diff -r b85f635f1eb5 -r 26b90b110719 Applications/StoneApplicationContext.h --- a/Applications/StoneApplicationContext.h Thu Nov 29 15:11:19 2018 +0100 +++ b/Applications/StoneApplicationContext.h Thu Nov 29 19:25:15 2018 +0100 @@ -22,6 +22,7 @@ #pragma once #include "../Framework/Toolbox/IWebService.h" +#include "../Framework/Toolbox/IDelayedCallExecutor.h" #include "../Framework/Toolbox/OrthancApiClient.h" #include "../Framework/Viewport/WidgetViewport.h" @@ -41,6 +42,7 @@ private: MessageBroker& broker_; IWebService* webService_; + IDelayedCallExecutor* delayedCallExecutor_; std::auto_ptr orthanc_; std::string orthancBaseUrl_; @@ -49,7 +51,8 @@ public: StoneApplicationContext(MessageBroker& broker) : broker_(broker), - webService_(NULL) + webService_(NULL), + delayedCallExecutor_(NULL) { } @@ -74,5 +77,15 @@ void SetWebService(IWebService& webService); void SetOrthancBaseUrl(const std::string& baseUrl); + + void SetDelayedCallExecutor(IDelayedCallExecutor& delayedCallExecutor) + { + delayedCallExecutor_ = &delayedCallExecutor; + } + + IDelayedCallExecutor& GetDelayedCallExecutor() + { + return *delayedCallExecutor_; + } }; } diff -r b85f635f1eb5 -r 26b90b110719 Framework/Radiography/RadiographyScene.cpp --- a/Framework/Radiography/RadiographyScene.cpp Thu Nov 29 15:11:19 2018 +0100 +++ b/Framework/Radiography/RadiographyScene.cpp Thu Nov 29 19:25:15 2018 +0100 @@ -423,7 +423,7 @@ const AffineTransform2D& viewTransform, ImageInterpolation interpolation) const { - Orthanc::ImageProcessing::Set(buffer, 0); // TODO: get background color (depending on inverted state) + Orthanc::ImageProcessing::Set(buffer, 0); // Render layers in the background-to-foreground order for (size_t index = 0; index < countLayers_; index++) diff -r b85f635f1eb5 -r 26b90b110719 Framework/StoneEnumerations.h --- a/Framework/StoneEnumerations.h Thu Nov 29 15:11:19 2018 +0100 +++ b/Framework/StoneEnumerations.h Thu Nov 29 19:25:15 2018 +0100 @@ -162,6 +162,8 @@ MessageType_ViewportChanged, + MessageType_Timeout, + // used in unit tests only MessageType_Test1, MessageType_Test2, diff -r b85f635f1eb5 -r 26b90b110719 Framework/Toolbox/IDelayedCallExecutor.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Toolbox/IDelayedCallExecutor.h Thu Nov 29 19:25:15 2018 +0100 @@ -0,0 +1,59 @@ +/** + * 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 . + **/ + + +#pragma once + +#include "../../Framework/Messages/IObserver.h" +#include "../../Framework/Messages/ICallable.h" + +#include +#include + +#include +#include + +namespace OrthancStone +{ + // The IDelayedCall executes a callback after a delay (equivalent to timeout() function in javascript). + class IDelayedCallExecutor : public boost::noncopyable + { + protected: + MessageBroker& broker_; + + public: + + typedef NoPayloadMessage TimeoutMessage; + + IDelayedCallExecutor(MessageBroker& broker) : + broker_(broker) + { + } + + + virtual ~IDelayedCallExecutor() + { + } + + + virtual void Schedule(MessageHandler* callback, + unsigned int timeoutInMs = 1000) = 0; + }; +} diff -r b85f635f1eb5 -r 26b90b110719 Platforms/Generic/DelayedCallCommand.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Generic/DelayedCallCommand.cpp Thu Nov 29 19:25:15 2018 +0100 @@ -0,0 +1,66 @@ +/** + * 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 . + **/ + + +#include "DelayedCallCommand.h" +#include "boost/thread/thread.hpp" + +#include + +namespace OrthancStone +{ + DelayedCallCommand::DelayedCallCommand(MessageBroker& broker, + MessageHandler* callback, // takes ownership + unsigned int timeoutInMs, + Orthanc::IDynamicObject* payload /* takes ownership */, + NativeStoneApplicationContext& context + ) : + IObservable(broker), + callback_(callback), + payload_(payload), + context_(context), + expirationTimePoint_(boost::chrono::system_clock::now() + boost::chrono::milliseconds(timeoutInMs)), + timeoutInMs_(timeoutInMs) + { + } + + + void DelayedCallCommand::Execute() + { + while (boost::chrono::system_clock::now() < expirationTimePoint_) + { + boost::this_thread::sleep_for(boost::chrono::milliseconds(1)); + } + } + + void DelayedCallCommand::Commit() + { + // We want to make sure that, i.e, the UpdateThread is not + // triggered while we are updating the "model" with the result of + // an OracleCommand + NativeStoneApplicationContext::GlobalMutexLocker lock(context_); + + if (callback_.get() != NULL) + { + IDelayedCallExecutor::TimeoutMessage message; // TODO: add payload + callback_->Apply(message); + } + } +} diff -r b85f635f1eb5 -r 26b90b110719 Platforms/Generic/DelayedCallCommand.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Generic/DelayedCallCommand.h Thu Nov 29 19:25:15 2018 +0100 @@ -0,0 +1,56 @@ +/** + * 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 . + **/ + + +#pragma once + +#include "IOracleCommand.h" + +#include "../../Framework/Toolbox/IDelayedCallExecutor.h" +#include "../../Framework/Messages/IObservable.h" +#include "../../Framework/Messages/ICallable.h" +#include "../../Applications/Generic/NativeStoneApplicationContext.h" +#include + +namespace OrthancStone +{ + class DelayedCallCommand : public IOracleCommand, IObservable + { + protected: + std::auto_ptr > callback_; + std::auto_ptr payload_; + NativeStoneApplicationContext& context_; + boost::chrono::system_clock::time_point expirationTimePoint_; + unsigned int timeoutInMs_; + + public: + DelayedCallCommand(MessageBroker& broker, + MessageHandler* callback, // takes ownership + unsigned int timeoutInMs, + Orthanc::IDynamicObject* payload /* takes ownership */, + NativeStoneApplicationContext& context + ); + + virtual void Execute(); + + virtual void Commit(); + }; + +} diff -r b85f635f1eb5 -r 26b90b110719 Platforms/Generic/OracleDelayedCallExecutor.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Generic/OracleDelayedCallExecutor.h Thu Nov 29 19:25:15 2018 +0100 @@ -0,0 +1,54 @@ +/** + * 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 . + **/ + + +#pragma once + +#include "../../Framework/Toolbox/IDelayedCallExecutor.h" +#include "Oracle.h" +#include "../../Applications/Generic/NativeStoneApplicationContext.h" +#include "DelayedCallCommand.h" + +namespace OrthancStone +{ + // The OracleTimeout executes callbacks after a delay. + class OracleDelayedCallExecutor : public IDelayedCallExecutor + { + private: + Oracle& oracle_; + NativeStoneApplicationContext& context_; + + public: + OracleDelayedCallExecutor(MessageBroker& broker, + Oracle& oracle, + NativeStoneApplicationContext& context) : + IDelayedCallExecutor(broker), + oracle_(oracle), + context_(context) + { + } + + virtual void Schedule(MessageHandler* callback, + unsigned int timeoutInMs = 1000) + { + oracle_.Submit(new DelayedCallCommand(broker_, callback, timeoutInMs, NULL, context_)); + } + }; +} diff -r b85f635f1eb5 -r 26b90b110719 Platforms/Wasm/Defaults.cpp --- a/Platforms/Wasm/Defaults.cpp Thu Nov 29 15:11:19 2018 +0100 +++ b/Platforms/Wasm/Defaults.cpp Thu Nov 29 19:25:15 2018 +0100 @@ -1,6 +1,7 @@ #include "Defaults.h" #include "WasmWebService.h" +#include "WasmDelayedCallExecutor.h" #include #include "Framework/Widgets/TestCairoWidget.h" #include @@ -73,6 +74,7 @@ application.reset(CreateUserApplication(broker)); applicationWasmAdapter.reset(CreateWasmApplicationAdapter(broker, application.get())); WasmWebService::SetBroker(broker); + WasmDelayedCallExecutor::SetBroker(broker); startupParametersBuilder.Clear(); } @@ -96,6 +98,7 @@ context->SetOrthancBaseUrl(baseUri); printf("Base URL to Orthanc API: [%s]\n", baseUri); context->SetWebService(OrthancStone::WasmWebService::GetInstance()); + context->SetDelayedCallExecutor(OrthancStone::WasmDelayedCallExecutor::GetInstance()); application->Initialize(context.get(), statusBar_, parameters); application->InitializeWasm(); diff -r b85f635f1eb5 -r 26b90b110719 Platforms/Wasm/WasmDelayedCallExecutor.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/WasmDelayedCallExecutor.cpp Thu Nov 29 19:25:15 2018 +0100 @@ -0,0 +1,46 @@ +#include "WasmDelayedCallExecutor.h" +#include "json/value.h" +#include "json/writer.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + + extern void WasmDelayedCallExecutor_Schedule(void* callable, + unsigned int timeoutInMs + /*void* payload*/); + + void EMSCRIPTEN_KEEPALIVE WasmDelayedCallExecutor_ExecuteCallback(void* callable + //void* payload + ) + { + if (callable == NULL) + { + throw; + } + else + { + reinterpret_cast*>(callable)-> + Apply(OrthancStone::IDelayedCallExecutor::TimeoutMessage()); // uri, reinterpret_cast(payload))); + } + } + + +#ifdef __cplusplus +} +#endif + + + +namespace OrthancStone +{ + MessageBroker* WasmDelayedCallExecutor::broker_ = NULL; + + + void WasmDelayedCallExecutor::Schedule(MessageHandler* callback, + unsigned int timeoutInMs) + { + WasmDelayedCallExecutor_Schedule(callback, timeoutInMs); + } +} diff -r b85f635f1eb5 -r 26b90b110719 Platforms/Wasm/WasmDelayedCallExecutor.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/WasmDelayedCallExecutor.h Thu Nov 29 19:25:15 2018 +0100 @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +namespace OrthancStone +{ + class WasmDelayedCallExecutor : public IDelayedCallExecutor + { + private: + static MessageBroker* broker_; + + // Private constructor => Singleton design pattern + WasmDelayedCallExecutor(MessageBroker& broker) : + IDelayedCallExecutor(broker) + { + } + + public: + static WasmDelayedCallExecutor& GetInstance() + { + if (broker_ == NULL) + { + printf("WasmDelayedCallExecutor::GetInstance(): broker not initialized\n"); + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + static WasmDelayedCallExecutor instance(*broker_); + return instance; + } + + static void SetBroker(MessageBroker& broker) + { + broker_ = &broker; + } + + virtual void Schedule(MessageHandler* callback, + unsigned int timeoutInMs = 1000); + + }; +} diff -r b85f635f1eb5 -r 26b90b110719 Platforms/Wasm/WasmDelayedCallExecutor.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/WasmDelayedCallExecutor.js Thu Nov 29 19:25:15 2018 +0100 @@ -0,0 +1,7 @@ +mergeInto(LibraryManager.library, { + WasmDelayedCallExecutor_Schedule: function(callable, timeoutInMs/*, payload*/) { + setTimeout(function() { + WasmDelayedCallExecutor_ExecuteCallback(callable/*, payload*/); + }, timeoutInMs); + } +}); diff -r b85f635f1eb5 -r 26b90b110719 Platforms/Wasm/WasmWebService.h --- a/Platforms/Wasm/WasmWebService.h Thu Nov 29 15:11:19 2018 +0100 +++ b/Platforms/Wasm/WasmWebService.h Thu Nov 29 19:25:15 2018 +0100 @@ -55,12 +55,12 @@ MessageHandler* failureCallable = NULL, unsigned int timeoutInSeconds = 60); - virtual void Start() - { - } + // virtual void Start() + // { + // } - virtual void Stop() - { - } + // virtual void Stop() + // { + // } }; } diff -r b85f635f1eb5 -r 26b90b110719 Platforms/Wasm/wasm-application-runner.ts --- a/Platforms/Wasm/wasm-application-runner.ts Thu Nov 29 15:11:19 2018 +0100 +++ b/Platforms/Wasm/wasm-application-runner.ts Thu Nov 29 19:25:15 2018 +0100 @@ -10,6 +10,7 @@ // global functions var WasmWebService_NotifyError: Function = null; var WasmWebService_NotifySuccess: Function = null; +var WasmDelayedCallExecutor_ExecuteCallback: Function = null; var WasmDoAnimation: Function = null; var SetStartupParameter: Function = null; var CreateWasmApplication: Function = null; @@ -93,6 +94,7 @@ WasmWebService_NotifySuccess = StoneFrameworkModule.cwrap('WasmWebService_NotifySuccess', null, ['number', 'string', 'array', 'number', 'number']); WasmWebService_NotifyError = StoneFrameworkModule.cwrap('WasmWebService_NotifyError', null, ['number', 'string', 'number']); + WasmDelayedCallExecutor_ExecuteCallback = StoneFrameworkModule.cwrap('WasmDelayedCallExecutor_ExecuteCallback', null, ['number']); WasmDoAnimation = StoneFrameworkModule.cwrap('WasmDoAnimation', null, []); SendMessageToStoneApplication = StoneFrameworkModule.cwrap('SendMessageToStoneApplication', 'string', ['string']); diff -r b85f635f1eb5 -r 26b90b110719 Resources/CMake/OrthancStoneConfiguration.cmake --- a/Resources/CMake/OrthancStoneConfiguration.cmake Thu Nov 29 15:11:19 2018 +0100 +++ b/Resources/CMake/OrthancStoneConfiguration.cmake Thu Nov 29 19:25:15 2018 +0100 @@ -186,8 +186,10 @@ ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServiceGetCommand.cpp ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServicePostCommand.cpp ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServiceDeleteCommand.cpp + ${ORTHANC_STONE_ROOT}/Platforms/Generic/DelayedCallCommand.cpp ${ORTHANC_STONE_ROOT}/Platforms/Generic/Oracle.cpp ${ORTHANC_STONE_ROOT}/Platforms/Generic/OracleWebService.h + ${ORTHANC_STONE_ROOT}/Platforms/Generic/OracleDelayedCallExecutor.h ) if (ENABLE_SDL OR ENABLE_QT) @@ -212,6 +214,7 @@ set(STONE_WASM_SOURCES ${ORTHANC_STONE_ROOT}/Platforms/Wasm/Defaults.cpp + ${ORTHANC_STONE_ROOT}/Platforms/Wasm/WasmDelayedCallExecutor.cpp ${ORTHANC_STONE_ROOT}/Platforms/Wasm/WasmWebService.cpp ${ORTHANC_STONE_ROOT}/Platforms/Wasm/WasmViewport.cpp ${ORTHANC_STONE_ROOT}/Platforms/Wasm/WasmPlatformApplicationAdapter.cpp @@ -226,6 +229,10 @@ COMMAND ${CMAKE_COMMAND} -E touch "${AUTOGENERATED_DIR}/WasmWebService.c" "" DEPENDS "${ORTHANC_STONE_ROOT}/Platforms/Wasm/WasmWebService.js") add_custom_command( + OUTPUT "${AUTOGENERATED_DIR}/WasmDelayedCallExecutor.c" + COMMAND ${CMAKE_COMMAND} -E touch "${AUTOGENERATED_DIR}/WasmDelayedCallExecutor.c" "" + DEPENDS "${ORTHANC_STONE_ROOT}/Platforms/Wasm/WasmDelayedCallExecutor.js") + add_custom_command( OUTPUT "${AUTOGENERATED_DIR}/default-library.c" COMMAND ${CMAKE_COMMAND} -E touch "${AUTOGENERATED_DIR}/default-library.c" "" DEPENDS "${ORTHANC_STONE_ROOT}/Platforms/Wasm/default-library.js") @@ -271,6 +278,7 @@ ${ORTHANC_STONE_ROOT}/Framework/Toolbox/Extent2D.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/FiniteProjectiveCamera.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/GeometryToolbox.cpp + ${ORTHANC_STONE_ROOT}/Framework/Toolbox/IDelayedCallExecutor.h ${ORTHANC_STONE_ROOT}/Framework/Toolbox/IWebService.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ImageGeometry.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/LinearAlgebra.cpp