changeset 2279:6572b73c65c5 refactoring

begin refactoring oracle
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 17 Sep 2026 20:10:13 +0200
parents 81400c2b9cae
children 7a169e34a033
files Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake OrthancStone/Resources/CMake/OrthancStoneWebAssemblyConfiguration.cmake OrthancStone/Sources/Loaders/ILoadersContext.h OrthancStone/Sources/Oracle/IEnvironment.h OrthancStone/Sources/Oracle/IOracle.h OrthancStone/Sources/Oracle/IOracleClient.h OrthancStone/Sources/Oracle/ThreadedOracle.cpp OrthancStone/Sources/Oracle/ThreadedOracle.h OrthancStone/Sources/Platforms/Native/NativeEnvironment.cpp OrthancStone/Sources/Platforms/Native/NativeEnvironment.h OrthancStone/Sources/Platforms/Native/RunnableThread.cpp OrthancStone/Sources/Platforms/Native/RunnableThread.h OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyEnvironment.cpp OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyEnvironment.h OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOracle.cpp OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOracle.h
diffstat 18 files changed, 898 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp	Fri Aug 14 11:01:24 2026 +0200
+++ b/Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp	Thu Sep 17 20:10:13 2026 +0200
@@ -54,6 +54,39 @@
 static int frameIndex = 0;
 
 
+
+// TODO Refactoring
+#include "../../../../OrthancStone/Sources/Platforms/Native/NativeEnvironment.h"
+#include "../../../../OrthancStone/Sources/Oracle/SleepOracleCommand.h"
+#include "../../../../OrthancStone/Sources/Oracle/ThreadedOracle.h"
+
+static OrthancStone::NativeEnvironment  environment_;
+static OrthancStone::New::ThreadedOracle oracle_(environment_, 4 /* threads */);
+
+
+class Toto : public OrthancStone::IOracleClient
+{
+public:
+  virtual void HandleSuccessFromOracle(const OrthancStone::IOracleCommand& command,
+                                       const Orthanc::IDynamicObject& result)
+  {
+    LOG(ERROR) << "success!";
+  }
+
+  virtual void HandleErrorFromOracle(const OrthancStone::IOracleCommand& command,
+                                     const Orthanc::OrthancException& error)
+  {
+    LOG(ERROR) << "error!";
+  }
+};
+
+static boost::shared_ptr<Toto> toto_(new Toto);
+// END TODO Refactoring
+
+
+
+
+
 static void ProcessOptions(int argc, char* argv[])
 {
   namespace po = boost::program_options;
@@ -181,6 +214,8 @@
 
       context.StartOracle();
 
+      environment_.Start();
+
       {
         {
           std::string font;
@@ -261,6 +296,11 @@
               {
                 switch (event.key.keysym.sym)
                 {
+                  case SDLK_b:
+                    // TODO Refactoring
+                    oracle_.Submit(toto_, new OrthancStone::SleepOracleCommand(1000));
+                    break;
+
                   case SDLK_f:
                     viewport->ToggleMaximize();
                     break;
@@ -519,6 +559,8 @@
           }
         }
         context.StopOracle();
+
+        environment_.Stop();
       }
     }
 
--- a/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Fri Aug 14 11:01:24 2026 +0200
+++ b/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Thu Sep 17 20:10:13 2026 +0200
@@ -4872,6 +4872,35 @@
 }
 
 
+
+// TODO Refactoring
+#include "../../../OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyEnvironment.h"
+#include "../../../OrthancStone/Sources/Oracle/SleepOracleCommand.h"
+
+static OrthancStone::WebAssemblyEnvironment  environment_;
+static OrthancStone::New::WebAssemblyOracle  oracle_(environment_);
+
+class Toto : public OrthancStone::IOracleClient
+{
+public:
+  virtual void HandleSuccessFromOracle(const OrthancStone::IOracleCommand& command,
+                                       const Orthanc::IDynamicObject& result)
+  {
+    LOG(ERROR) << "success!";
+  }
+
+  virtual void HandleErrorFromOracle(const OrthancStone::IOracleCommand& command,
+                                     const Orthanc::OrthancException& error)
+  {
+    LOG(ERROR) << "error!";
+  }
+};
+
+static boost::shared_ptr<Toto> toto_(new Toto);
+// END TODO Refactoring
+
+
+
 extern "C"
 {
   int main(int argc, char const *argv[]) 
@@ -4905,6 +4934,10 @@
     }
 
     DISPATCH_JAVASCRIPT_EVENT("StoneInitialized");
+
+
+    // TODO Refactoring
+    oracle_.Submit(toto_, new OrthancStone::SleepOracleCommand(2000));
   }
 
 
--- a/OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake	Fri Aug 14 11:01:24 2026 +0200
+++ b/OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake	Thu Sep 17 20:10:13 2026 +0200
@@ -209,6 +209,8 @@
     ${ORTHANC_STONE_ROOT}/Loaders/GenericLoadersContext.cpp
     ${ORTHANC_STONE_ROOT}/Oracle/GenericOracleRunner.cpp
     ${ORTHANC_STONE_ROOT}/Oracle/ThreadedOracle.cpp
+    ${ORTHANC_STONE_ROOT}/Platforms/Native/NativeEnvironment.cpp
+    ${ORTHANC_STONE_ROOT}/Platforms/Native/RunnableThread.cpp
     )
 endif()
 
--- a/OrthancStone/Resources/CMake/OrthancStoneWebAssemblyConfiguration.cmake	Fri Aug 14 11:01:24 2026 +0200
+++ b/OrthancStone/Resources/CMake/OrthancStoneWebAssemblyConfiguration.cmake	Thu Sep 17 20:10:13 2026 +0200
@@ -48,6 +48,7 @@
 
 list(APPEND ORTHANC_STONE_SOURCES
   ${CMAKE_CURRENT_LIST_DIR}/../../Sources/Platforms/WebAssembly/WebAssemblyCairoViewport.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/../../Sources/Platforms/WebAssembly/WebAssemblyEnvironment.cpp
   ${CMAKE_CURRENT_LIST_DIR}/../../Sources/Platforms/WebAssembly/WebAssemblyLoadersContext.cpp
   ${CMAKE_CURRENT_LIST_DIR}/../../Sources/Platforms/WebAssembly/WebAssemblyOracle.cpp
   ${CMAKE_CURRENT_LIST_DIR}/../../Sources/Platforms/WebAssembly/WebAssemblyViewport.cpp
--- a/OrthancStone/Sources/Loaders/ILoadersContext.h	Fri Aug 14 11:01:24 2026 +0200
+++ b/OrthancStone/Sources/Loaders/ILoadersContext.h	Thu Sep 17 20:10:13 2026 +0200
@@ -70,6 +70,7 @@
                             int priority,
                             IOracleCommand* command /* Takes ownership */) = 0;
 
+    private:
       /**
        * Cancel all the commands that are waiting in the
        * "OracleScheduler" queue and that are linked to the given
@@ -91,6 +92,9 @@
        **/
       virtual void CancelAllRequests() = 0;
 
+    public:
+      // TODO Refactoring - Remove this
+
       /**
        * Add a reference to the given observer in the Stone loaders
        * context. This can be used to match the lifetime of a loader
@@ -102,6 +106,7 @@
        **/
       virtual void AddLoader(boost::shared_ptr<IObserver> loader) = 0;
 
+    private:
       /**
        * Returns the number of commands that were scheduled and
        * processed using the "Schedule()" method. By "processed"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Oracle/IEnvironment.h	Thu Sep 17 20:10:13 2026 +0200
@@ -0,0 +1,48 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IOracleClient.h"
+
+#include <boost/weak_ptr.hpp>
+
+
+namespace OrthancStone
+{
+  class IEnvironment : public boost::noncopyable
+  {
+  public:
+    virtual ~IEnvironment()
+    {
+    }
+
+    virtual void NotifyOracleSuccess(const boost::weak_ptr<IOracleClient>& client,
+                                     IOracleCommand* command /* takes ownership */,
+                                     Orthanc::IDynamicObject* result /* takes ownership */) = 0;
+
+    virtual void NotifyOracleError(const boost::weak_ptr<IOracleClient>& client,
+                                   IOracleCommand* command /* takes ownership */,
+                                   const Orthanc::OrthancException& error) = 0;
+  };
+}
--- a/OrthancStone/Sources/Oracle/IOracle.h	Fri Aug 14 11:01:24 2026 +0200
+++ b/OrthancStone/Sources/Oracle/IOracle.h	Thu Sep 17 20:10:13 2026 +0200
@@ -25,6 +25,9 @@
 
 #include "../Messages/IObserver.h"
 #include "IOracleCommand.h"
+#include "IOracleClient.h"
+
+#include <OrthancException.h>
 
 #include <boost/shared_ptr.hpp>
 
@@ -45,4 +48,19 @@
     virtual bool Schedule(boost::shared_ptr<IObserver> receiver,
                           IOracleCommand* command) = 0;  // Takes ownership
   };
+
+
+  namespace New
+  {
+    class IOracle : public boost::noncopyable
+    {
+    public:
+      virtual ~IOracle()
+      {
+      }
+
+      virtual void Submit(const boost::shared_ptr<IOracleClient>& client,
+                          IOracleCommand* command /* takes ownership */) = 0;
+    };
+  }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Oracle/IOracleClient.h	Thu Sep 17 20:10:13 2026 +0200
@@ -0,0 +1,46 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IOracleCommand.h"
+
+#include <OrthancException.h>
+
+
+namespace OrthancStone
+{
+  class IOracleClient : public boost::noncopyable
+  {
+  public:
+    virtual ~IOracleClient()
+    {
+    }
+
+    virtual void HandleSuccessFromOracle(const IOracleCommand& command,
+                                         const Orthanc::IDynamicObject& result) = 0;
+
+    virtual void HandleErrorFromOracle(const IOracleCommand& command,
+                                       const Orthanc::OrthancException& error) = 0;
+  };
+}
--- a/OrthancStone/Sources/Oracle/ThreadedOracle.cpp	Fri Aug 14 11:01:24 2026 +0200
+++ b/OrthancStone/Sources/Oracle/ThreadedOracle.cpp	Thu Sep 17 20:10:13 2026 +0200
@@ -432,4 +432,61 @@
       }
     }
   }
+
+
+
+  namespace New
+  {
+    class ThreadedOracle::SleepRunnable : public Orthanc::IRunnable
+    {
+    private:
+
+
+    public:
+      virtual void Run() ORTHANC_OVERRIDE
+      {
+      }
+    };
+
+
+    ThreadedOracle::ThreadedOracle(IEnvironment& environment,
+                                   unsigned int countWorkers) :
+      environment_(environment),
+      sleepingThread_(new SleepRunnable, 50 /* milliseconds */)
+    {
+      threadPool_.SetThreadsCount(countWorkers);
+      threadPool_.SetDequeueTimeout(50);
+    }
+
+
+    void ThreadedOracle::Submit(const boost::shared_ptr<IOracleClient>& client,
+                                IOracleCommand* command /* takes ownership */)
+    {
+      std::unique_ptr<IOracleCommand> protection(command);
+
+      if (command == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+
+      try
+      {
+        switch (command->GetType())
+        {
+          default:
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented,
+                                            "Command type not implemented by the Threaded Oracle: " +
+                                            boost::lexical_cast<std::string>(command->GetType()));
+        }
+      }
+      catch (Orthanc::OrthancException& e)
+      {
+        environment_.NotifyOracleError(client, protection.release(), e);
+      }
+      catch (...)
+      {
+        environment_.NotifyOracleError(client, protection.release(), Orthanc::OrthancException(Orthanc::ErrorCode_InternalError));
+      }
+    }
+  }
 }
--- a/OrthancStone/Sources/Oracle/ThreadedOracle.h	Fri Aug 14 11:01:24 2026 +0200
+++ b/OrthancStone/Sources/Oracle/ThreadedOracle.h	Thu Sep 17 20:10:13 2026 +0200
@@ -42,11 +42,14 @@
 #  include "../Toolbox/ParsedDicomCache.h"
 #endif
 
-#include "IOracle.h"
+#include "../Messages/IMessageEmitter.h"
+#include "../Platforms/Native/RunnableThread.h"
 #include "GenericOracleRunner.h"
-#include "../Messages/IMessageEmitter.h"
+#include "IEnvironment.h"
+#include "IOracle.h"
 
 #include <MultiThreading/SharedMessageQueue.h>
+#include <MultiThreading/ThreadPool.h>
 
 
 namespace OrthancStone
@@ -112,4 +115,38 @@
     virtual bool Schedule(boost::shared_ptr<IObserver> receiver,
                           IOracleCommand* command) ORTHANC_OVERRIDE;
   };
+
+
+  namespace New
+  {
+    class ThreadedOracle : public IOracle
+    {
+    private:
+      class SleepCommands;
+      class SleepRunnable;
+
+      IEnvironment&        environment_;
+      RunnableThread       sleepingThread_;
+      Orthanc::ThreadPool  threadPool_;
+
+    public:
+      ThreadedOracle(IEnvironment& environment,
+                     unsigned int countWorkers);
+
+      void Start()
+      {
+        sleepingThread_.Start();
+        threadPool_.Start();
+      }
+
+      void Stop()
+      {
+        threadPool_.Stop();
+        sleepingThread_.Stop();
+      }
+
+      virtual void Submit(const boost::shared_ptr<IOracleClient>& client,
+                          IOracleCommand* command /* takes ownership */) ORTHANC_OVERRIDE;
+    };
+  }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Platforms/Native/NativeEnvironment.cpp	Thu Sep 17 20:10:13 2026 +0200
@@ -0,0 +1,150 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "NativeEnvironment.h"
+
+
+namespace OrthancStone
+{
+  class NativeEnvironment::Completion : public Orthanc::IDynamicObject
+  {
+  protected:
+    boost::weak_ptr<IOracleClient>   client_;
+    std::unique_ptr<IOracleCommand>  command_;
+
+  public:
+    Completion(const boost::weak_ptr<IOracleClient>& client,
+               IOracleCommand* command /* takes ownership */) :
+      client_(client),
+      command_(command)
+    {
+      if (command == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+    }
+
+    virtual void NotifyClient() = 0;
+  };
+
+
+  class NativeEnvironment::SuccessCompletion : public Completion
+  {
+  private:
+    std::unique_ptr<Orthanc::IDynamicObject> result_;
+
+  public:
+    SuccessCompletion(const boost::weak_ptr<IOracleClient>& client,
+                      IOracleCommand* command /* takes ownership */,
+                      Orthanc::IDynamicObject* result /* takes ownership */) :
+      Completion(client, command),
+      result_(result)
+    {
+      if (result == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+    }
+
+    virtual void NotifyClient() ORTHANC_OVERRIDE
+    {
+      boost::shared_ptr<IOracleClient> lock(client_.lock());
+
+      if (lock)
+      {
+        lock->HandleSuccessFromOracle(*command_, *result_);
+      }
+    }
+  };
+
+
+  class NativeEnvironment::ErrorCompletion : public Completion
+  {
+  private:
+    Orthanc::OrthancException  error_;
+
+  public:
+    ErrorCompletion(const boost::weak_ptr<IOracleClient>& client,
+                    IOracleCommand* command /* takes ownership */,
+                    const Orthanc::OrthancException& error) :
+      Completion(client, command),
+      error_(error)
+    {
+    }
+
+    virtual void NotifyClient() ORTHANC_OVERRIDE
+    {
+      boost::shared_ptr<IOracleClient> lock(client_.lock());
+
+      if (lock)
+      {
+        lock->HandleErrorFromOracle(*command_, error_);
+      }
+    }
+  };
+
+
+  class NativeEnvironment::OracleRunnable : public Orthanc::IRunnable
+  {
+  private:
+    Orthanc::SharedMessageQueue& queue_;
+
+  public:
+    OracleRunnable(Orthanc::SharedMessageQueue& queue) :
+      queue_(queue)
+    {
+    }
+
+    virtual void Run() ORTHANC_OVERRIDE
+    {
+      std::unique_ptr<Orthanc::IDynamicObject> completion(queue_.Dequeue(50 /* milliseconds */));
+
+      if (completion.get() != NULL)
+      {
+        dynamic_cast<Completion&>(*completion).NotifyClient();
+      }
+    }
+  };
+
+
+  NativeEnvironment::NativeEnvironment() :
+    oracleThread_(new OracleRunnable(oracleQueue_), 0 /* time resolution is in Dequeue() */)
+  {
+  }
+
+
+  void NativeEnvironment::NotifyOracleSuccess(const boost::weak_ptr<IOracleClient>& client,
+                                              IOracleCommand* command /* takes ownership */,
+                                              Orthanc::IDynamicObject* result /* takes ownership */)
+  {
+    oracleQueue_.Enqueue(new SuccessCompletion(client, command, result));
+  }
+
+
+  void NativeEnvironment::NotifyOracleError(const boost::weak_ptr<IOracleClient>& client,
+                                            IOracleCommand* command /* takes ownership */,
+                                            const Orthanc::OrthancException& error)
+  {
+    oracleQueue_.Enqueue(new ErrorCompletion(client, command, error));
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Platforms/Native/NativeEnvironment.h	Thu Sep 17 20:10:13 2026 +0200
@@ -0,0 +1,67 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../Oracle/IEnvironment.h"
+#include "RunnableThread.h"
+
+#include <MultiThreading/SharedMessageQueue.h>
+
+
+namespace OrthancStone
+{
+  class NativeEnvironment : public IEnvironment
+  {
+  private:
+    class OracleRunnable;
+    class Completion;
+    class SuccessCompletion;
+    class ErrorCompletion;
+
+    boost::mutex                 mutex_;  // Main mutex of the application, to go single-threaded
+    Orthanc::SharedMessageQueue  oracleQueue_;
+    RunnableThread               oracleThread_;
+
+  public:
+    NativeEnvironment();
+
+    void Start()
+    {
+      oracleThread_.Start();
+    }
+
+    void Stop()
+    {
+      oracleThread_.Stop();
+    }
+
+    virtual void NotifyOracleSuccess(const boost::weak_ptr<IOracleClient>& client,
+                                     IOracleCommand* command /* takes ownership */,
+                                     Orthanc::IDynamicObject* result /* takes ownership */) ORTHANC_OVERRIDE;
+
+    virtual void NotifyOracleError(const boost::weak_ptr<IOracleClient>& client,
+                                   IOracleCommand* command /* takes ownership */,
+                                   const Orthanc::OrthancException& error) ORTHANC_OVERRIDE;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Platforms/Native/RunnableThread.cpp	Thu Sep 17 20:10:13 2026 +0200
@@ -0,0 +1,105 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "RunnableThread.h"
+
+#include <OrthancException.h>
+
+
+namespace OrthancStone
+{
+  void RunnableThread::Worker(RunnableThread* that)
+  {
+    for (;;)
+    {
+      {
+        boost::mutex::scoped_lock lock(that->mutex_);
+        if (that->state_ != State_Running)
+        {
+          return;
+        }
+      }
+
+      that->runnable_->Run();
+
+      if (that->timeResolution_ != 0)
+      {
+        boost::this_thread::sleep(boost::posix_time::milliseconds(that->timeResolution_));
+      }
+    }
+  }
+
+
+  void RunnableThread::StopInternal(bool throws)
+  {
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+
+      if (state_ != State_Running)
+      {
+        if (throws)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+        }
+      }
+      else
+      {
+        state_ = State_Done;
+      }
+    }
+
+    if (thread_.joinable())
+    {
+      thread_.join();
+    }
+  }
+
+
+  RunnableThread::RunnableThread(Orthanc::IRunnable* runnable,
+                                 unsigned int timeResolution) :
+    runnable_(runnable),
+    timeResolution_(timeResolution),
+    state_(State_Initialization)
+  {
+    if (runnable == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+  }
+
+
+  void RunnableThread::Start()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (state_ != State_Initialization)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      state_ = State_Running;
+      thread_ = boost::thread(Worker, this);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Platforms/Native/RunnableThread.h	Thu Sep 17 20:10:13 2026 +0200
@@ -0,0 +1,72 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <OrthancFramework.h>   // Needed before IRunnable in Orthanc <= 1.13.0
+
+#include <MultiThreading/IRunnable.h>
+
+#include <boost/thread.hpp>
+
+
+namespace OrthancStone
+{
+  // This is a thread that periodically executes a runnable
+  class RunnableThread : public boost::noncopyable
+  {
+  private:
+    enum State
+    {
+      State_Initialization,
+      State_Running,
+      State_Done
+    };
+
+    static void Worker(RunnableThread* that);
+
+    void StopInternal(bool throws);
+
+    boost::mutex                         mutex_;
+    std::unique_ptr<Orthanc::IRunnable>  runnable_;
+    unsigned int                         timeResolution_;
+    volatile State                       state_;
+    boost::thread                        thread_;
+
+  public:
+    RunnableThread(Orthanc::IRunnable* runnable,
+                   unsigned int timeResolution);
+
+    ~RunnableThread()
+    {
+      StopInternal(false /* no exception in destructors */);
+    }
+
+    void Start();
+
+    void Stop()
+    {
+      StopInternal(true);
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyEnvironment.cpp	Thu Sep 17 20:10:13 2026 +0200
@@ -0,0 +1,70 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "WebAssemblyEnvironment.h"
+
+namespace OrthancStone
+{
+  void WebAssemblyEnvironment::NotifyOracleSuccess(const boost::weak_ptr<IOracleClient>& client,
+                                                   IOracleCommand* command /* takes ownership */,
+                                                   Orthanc::IDynamicObject* result /* takes ownership */)
+  {
+    std::unique_ptr<IOracleCommand> protection(command);
+    std::unique_ptr<Orthanc::IDynamicObject> protection2(result);
+
+    if (command == NULL ||
+        result == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+    else
+    {
+      boost::shared_ptr<IOracleClient> lock(client.lock());
+      if (lock)
+      {
+        lock->HandleSuccessFromOracle(*protection, *protection2);
+      }
+    }
+  }
+
+
+  void WebAssemblyEnvironment::NotifyOracleError(const boost::weak_ptr<IOracleClient>& client,
+                                                 IOracleCommand* command /* takes ownership */,
+                                                 const Orthanc::OrthancException& error)
+  {
+    std::unique_ptr<IOracleCommand> protection(command);
+
+    if (command == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+    else
+    {
+      boost::shared_ptr<IOracleClient> lock(client.lock());
+      if (lock)
+      {
+        lock->HandleErrorFromOracle(*protection, error);
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyEnvironment.h	Thu Sep 17 20:10:13 2026 +0200
@@ -0,0 +1,41 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../Oracle/IEnvironment.h"
+
+namespace OrthancStone
+{
+  class WebAssemblyEnvironment : public IEnvironment
+  {
+  public:
+    virtual void NotifyOracleSuccess(const boost::weak_ptr<IOracleClient>& client,
+                                     IOracleCommand* command /* takes ownership */,
+                                     Orthanc::IDynamicObject* result /* takes ownership */) ORTHANC_OVERRIDE;
+
+    virtual void NotifyOracleError(const boost::weak_ptr<IOracleClient>& client,
+                                   IOracleCommand* command /* takes ownership */,
+                                   const Orthanc::OrthancException& error) ORTHANC_OVERRIDE;
+  };
+}
--- a/OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOracle.cpp	Fri Aug 14 11:01:24 2026 +0200
+++ b/OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOracle.cpp	Thu Sep 17 20:10:13 2026 +0200
@@ -875,4 +875,84 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
     }
   }
+
+
+
+  namespace New
+  {
+    class WebAssemblyOracle::TimeoutCallback
+    {
+    private:
+      IEnvironment&                        environment_;
+      boost::weak_ptr<IOracleClient>       client_;
+      std::unique_ptr<SleepOracleCommand>  command_;
+
+    public:
+      TimeoutCallback(IEnvironment& environment,
+                      const boost::weak_ptr<IOracleClient>& client,
+                      SleepOracleCommand* command) :
+        environment_(environment),
+        client_(client),
+        command_(command)
+      {
+        if (command == NULL)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+        }
+      }
+
+      void Notify()
+      {
+        assert(environment_.get() != NULL);
+        assert(command_.get() != NULL);
+        environment_.NotifyOracleSuccess(client_, command_.release(), new Orthanc::IDynamicObject);
+      }
+
+      static void Callback(void *userData)
+      {
+        std::unique_ptr<TimeoutCallback> callback(reinterpret_cast<TimeoutCallback*>(userData));
+        callback->Notify();
+      }
+    };
+
+
+    void WebAssemblyOracle::Submit(const boost::shared_ptr<IOracleClient>& client,
+                                   IOracleCommand* command /* takes ownership */)
+    {
+      // TODO Refactoring - Use "priority"
+
+      std::unique_ptr<IOracleCommand> protection(command);
+
+      if (command == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+
+      try
+      {
+        switch (command->GetType())
+        {
+          case IOracleCommand::Type_Sleep:
+          {
+            unsigned int timeoutMS = dynamic_cast<SleepOracleCommand*>(command)->GetDelay();
+            emscripten_set_timeout(TimeoutCallback::Callback, timeoutMS,
+                                   new TimeoutCallback(environment_, client, dynamic_cast<SleepOracleCommand*>(protection.release())));
+            break;
+          }
+
+          default:
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented,
+                                            "Command type not implemented by the WebAssembly Oracle: " + command->GetType());
+        }
+      }
+      catch (Orthanc::OrthancException& e)
+      {
+        environment_.NotifyOracleError(client, protection.release(), e);
+      }
+      catch (...)
+      {
+        environment_.NotifyOracleError(client, protection.release(), Orthanc::OrthancException(Orthanc::ErrorCode_InternalError));
+      }
+    }
+  }
 }
--- a/OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOracle.h	Fri Aug 14 11:01:24 2026 +0200
+++ b/OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOracle.h	Thu Sep 17 20:10:13 2026 +0200
@@ -35,6 +35,7 @@
 
 #include "../../Messages/IObservable.h"
 #include "../../Messages/IMessageEmitter.h"
+#include "../../Oracle/IEnvironment.h"
 #include "../../Oracle/IOracle.h"
 
 #if ORTHANC_ENABLE_DCMTK == 1
@@ -153,4 +154,25 @@
       bool HasPixelData() const;
     };    
   };
+
+
+  namespace New
+  {
+    class WebAssemblyOracle : public IOracle
+    {
+    private:
+      class TimeoutCallback;
+
+      IEnvironment&  environment_;
+
+    public:
+      WebAssemblyOracle(IEnvironment& environment) :
+        environment_(environment)
+      {
+      }
+
+      virtual void Submit(const boost::shared_ptr<IOracleClient>& client,
+                          IOracleCommand* command /* takes ownership */) ORTHANC_OVERRIDE;
+    };
+  }
 }