changeset 431:26b90b110719 am-vsol-upgrade

added DelayedCallExecutor to avoid using sleep() in C++ that consumes 100% CPU once executed in WASM
author am@osimis.io
date Thu, 29 Nov 2018 19:25:15 +0100
parents b85f635f1eb5
children 4eb96c6b4e96
files Applications/Generic/NativeStoneApplicationRunner.cpp Applications/Samples/CMakeLists.txt Applications/StoneApplicationContext.h Framework/Radiography/RadiographyScene.cpp Framework/StoneEnumerations.h Framework/Toolbox/IDelayedCallExecutor.h Platforms/Generic/DelayedCallCommand.cpp Platforms/Generic/DelayedCallCommand.h Platforms/Generic/OracleDelayedCallExecutor.h Platforms/Wasm/Defaults.cpp Platforms/Wasm/WasmDelayedCallExecutor.cpp Platforms/Wasm/WasmDelayedCallExecutor.h Platforms/Wasm/WasmDelayedCallExecutor.js Platforms/Wasm/WasmWebService.h Platforms/Wasm/wasm-application-runner.ts Resources/CMake/OrthancStoneConfiguration.cmake
diffstat 16 files changed, 370 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- 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 <Core/Logging.h>
@@ -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);
 
           {
--- 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
--- 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<OrthancApiClient>  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_;
+    }
   };
 }
--- 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++)
--- 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,
--- /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 <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../Framework/Messages/IObserver.h"
+#include "../../Framework/Messages/ICallable.h"
+
+#include <Core/IDynamicObject.h>
+#include <Core/Logging.h>
+
+#include <string>
+#include <map>
+
+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<MessageType_Timeout> TimeoutMessage;
+
+    IDelayedCallExecutor(MessageBroker& broker) :
+      broker_(broker)
+    {
+    }
+
+    
+    virtual ~IDelayedCallExecutor()
+    {
+    }
+
+    
+    virtual void Schedule(MessageHandler<IDelayedCallExecutor::TimeoutMessage>* callback,
+                         unsigned int timeoutInMs = 1000) = 0;
+  };
+}
--- /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 <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "DelayedCallCommand.h"
+#include "boost/thread/thread.hpp"
+
+#include <iostream>
+
+namespace OrthancStone
+{
+  DelayedCallCommand::DelayedCallCommand(MessageBroker& broker,
+                                         MessageHandler<IDelayedCallExecutor::TimeoutMessage>* 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);
+    }
+  }
+}
--- /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 <http://www.gnu.org/licenses/>.
+ **/
+
+
+#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 <boost/chrono.hpp>
+
+namespace OrthancStone
+{
+  class DelayedCallCommand : public IOracleCommand, IObservable
+  {
+  protected:
+    std::auto_ptr<MessageHandler<IDelayedCallExecutor::TimeoutMessage> >  callback_;
+    std::auto_ptr<Orthanc::IDynamicObject>  payload_;
+    NativeStoneApplicationContext&          context_;
+    boost::chrono::system_clock::time_point expirationTimePoint_;
+    unsigned int                            timeoutInMs_;
+
+  public:
+    DelayedCallCommand(MessageBroker& broker,
+                       MessageHandler<IDelayedCallExecutor::TimeoutMessage>* callback,  // takes ownership
+                       unsigned int timeoutInMs,
+                       Orthanc::IDynamicObject* payload /* takes ownership */,
+                       NativeStoneApplicationContext& context
+                       );
+
+    virtual void Execute();
+
+    virtual void Commit();
+  };
+
+}
--- /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 <http://www.gnu.org/licenses/>.
+ **/
+
+
+#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<IDelayedCallExecutor::TimeoutMessage>* callback,
+                          unsigned int timeoutInMs = 1000)
+    {
+      oracle_.Submit(new DelayedCallCommand(broker_, callback, timeoutInMs, NULL, context_));
+    }
+  };
+}
--- 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 <Framework/dev.h>
 #include "Framework/Widgets/TestCairoWidget.h"
 #include <Framework/Viewport/WidgetViewport.h>
@@ -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();
 
--- /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 <emscripten/emscripten.h>
+
+#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<OrthancStone::MessageHandler<OrthancStone::IDelayedCallExecutor::TimeoutMessage>*>(callable)->
+        Apply(OrthancStone::IDelayedCallExecutor::TimeoutMessage()); // uri, reinterpret_cast<Orthanc::IDynamicObject*>(payload)));
+    }
+  }
+
+
+#ifdef __cplusplus
+}
+#endif
+
+
+
+namespace OrthancStone
+{
+  MessageBroker* WasmDelayedCallExecutor::broker_ = NULL;
+
+
+  void WasmDelayedCallExecutor::Schedule(MessageHandler<IDelayedCallExecutor::TimeoutMessage>* callback,
+                         unsigned int timeoutInMs)
+  {
+    WasmDelayedCallExecutor_Schedule(callback, timeoutInMs);
+  }
+}
--- /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 <Framework/Toolbox/IDelayedCallExecutor.h>
+#include <Core/OrthancException.h>
+
+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<IDelayedCallExecutor::TimeoutMessage>* callback,
+                         unsigned int timeoutInMs = 1000);
+
+  };
+}
--- /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);
+  }
+});
--- 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<IWebService::HttpRequestErrorMessage>* failureCallable = NULL,
                              unsigned int timeoutInSeconds = 60);
 
-    virtual void Start()
-    {
-    }
+    // virtual void Start()
+    // {
+    // }
     
-    virtual void Stop()
-    {
-    }
+    // virtual void Stop()
+    // {
+    // }
   };
 }
--- 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']);
--- 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