changeset 2313:46e57967b285 refactoring

replacing ILoadersContext by StoneApplication
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 30 Sep 2026 11:50:48 +0200
parents ae031ceb217e
children 891aff56bbee
files Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewerApplication.h Applications/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewer.cpp Applications/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApplication.h Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp OrthancStone/Sources/Loaders/DicomResourcesLoader.cpp OrthancStone/Sources/Loaders/DicomResourcesLoader.h OrthancStone/Sources/Loaders/DicomVolumeLoader.cpp OrthancStone/Sources/Loaders/DicomVolumeLoader.h OrthancStone/Sources/Loaders/ILoaderFactory.h OrthancStone/Sources/Loaders/SeriesFramesLoader.cpp OrthancStone/Sources/Loaders/SeriesFramesLoader.h OrthancStone/Sources/Loaders/SeriesMetadataLoader.cpp OrthancStone/Sources/Loaders/SeriesMetadataLoader.h OrthancStone/Sources/Loaders/SeriesThumbnailsLoader.cpp OrthancStone/Sources/Loaders/SeriesThumbnailsLoader.h OrthancStone/Sources/StoneApplication.cpp OrthancStone/Sources/StoneApplication.h
diffstat 18 files changed, 171 insertions(+), 192 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp	Tue Sep 29 17:52:35 2026 +0200
+++ b/Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp	Wed Sep 30 11:50:48 2026 +0200
@@ -459,7 +459,7 @@
 #endif
 
         boost::shared_ptr<SdlSimpleViewerApplication> application(
-          SdlSimpleViewerApplication::Create(context, viewport));
+          SdlSimpleViewerApplication::Create(OrthancStone::StoneApplication::GetInstance(), viewport));
 
         OrthancStone::DicomSource source;
 
--- a/Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewerApplication.h	Tue Sep 29 17:52:35 2026 +0200
+++ b/Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewerApplication.h	Wed Sep 30 11:50:48 2026 +0200
@@ -27,6 +27,7 @@
 #include "../../../../OrthancStone/Sources/Loaders/SeriesFramesLoader.h"
 #include "../../../../OrthancStone/Sources/Loaders/SeriesThumbnailsLoader.h"
 #include "../../../../OrthancStone/Sources/Scene2DViewport/ViewportController.h"
+#include "../../../../OrthancStone/Sources/StoneApplication.h"
 #include "../../../../OrthancStone/Sources/Viewport/IViewport.h"
 
 #include <Compatibility.h>  // For std::unique_ptr<>
@@ -55,14 +56,12 @@
 {
 
 public:
-  static boost::shared_ptr<SdlSimpleViewerApplication> Create(ILoadersContext& context, boost::shared_ptr<IViewport> viewport)
+  static boost::shared_ptr<SdlSimpleViewerApplication> Create(OrthancStone::StoneApplication::Context& context,
+                                                              boost::shared_ptr<IViewport> viewport)
   {
     boost::shared_ptr<SdlSimpleViewerApplication> application(new SdlSimpleViewerApplication(context, viewport));
 
-    {
-      std::unique_ptr<ILoadersContext::ILock> lock(context.Lock());
-      application->dicomLoader_ = DicomResourcesLoader::Create(*lock);
-    }
+    application->dicomLoader_ = DicomResourcesLoader::Create(context);
 
     application->Register<DicomResourcesLoader::SuccessMessage>(*application->dicomLoader_, &SdlSimpleViewerApplication::Handle);
 
@@ -71,7 +70,7 @@
 
   void LoadOrthancFrame(const DicomSource& source, const std::string& instanceId, unsigned int frame)
   {
-    std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
+    std::unique_ptr<OrthancStone::IEnvironment::ILock> lock(context_.GetEnvironment().AcquireLock());
 
     dicomLoader_->ScheduleLoadOrthancResource(boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_SOP_INSTANCE_UID),
                                               0, source, Orthanc::ResourceType_Instance, instanceId,
@@ -108,13 +107,13 @@
   }
 
 private:
-  ILoadersContext& context_;
+  OrthancStone::StoneApplication::Context& context_;
   boost::shared_ptr<IViewport>             viewport_;
   boost::shared_ptr<DicomResourcesLoader>  dicomLoader_;
   boost::shared_ptr<SeriesFramesLoader>    framesLoader_;
   OrthancStone::Units                      units_;
 
-  SdlSimpleViewerApplication(ILoadersContext& context,
+  SdlSimpleViewerApplication(OrthancStone::StoneApplication::Context& context,
                              boost::shared_ptr<IViewport> viewport) :
     context_(context),
     viewport_(viewport),
@@ -173,11 +172,11 @@
     //message.GetResources()->GetResource(0).Print(stdout);
 
     {
-      std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
+      std::unique_ptr<OrthancStone::IEnvironment::ILock> lock(context_.GetEnvironment().AcquireLock());
+
       SeriesFramesLoader::Factory f(*message.GetResources());
 
-      framesLoader_ = boost::dynamic_pointer_cast<SeriesFramesLoader>(
-        f.Create(*lock));
+      framesLoader_ = boost::dynamic_pointer_cast<SeriesFramesLoader>(f.Create(context_));
       
       Register<SeriesFramesLoader::FrameLoadedMessage>(
         *framesLoader_, &SdlSimpleViewerApplication::Handle);
--- a/Applications/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewer.cpp	Tue Sep 29 17:52:35 2026 +0200
+++ b/Applications/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewer.cpp	Wed Sep 30 11:50:48 2026 +0200
@@ -71,8 +71,7 @@
 {
 }
 
-static std::unique_ptr<OrthancStone::WebAssemblyLoadersContext>  context_;
-static boost::shared_ptr<OrthancStone::Application>  application_;
+static boost::shared_ptr<OrthancStone::SingleFrameViewerApplication>  application_;
 
 extern "C"
 {
@@ -97,7 +96,6 @@
       configuration.SetDicomCacheSize(128 * 1024 * 1024);  // 128MB
 
       OrthancStone::StoneApplication::Initialize(configuration);
-      context_.reset(new OrthancStone::WebAssemblyLoadersContext(1, 4, 1));
   
       DISPATCH_JAVASCRIPT_EVENT("WasmModuleInitialized");
     }
@@ -111,12 +109,6 @@
   {
     try
     {
-      if (context_.get() == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls,
-                                        "The loaders context is not available yet");
-      }
-      
       if (application_.get() != NULL)
       {
         throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls,
@@ -125,7 +117,7 @@
 
       boost::shared_ptr<OrthancStone::WebGLViewport> viewport(
         OrthancStone::WebGLViewportsRegistry::GetGlobalRegistry().Add(canvasId));
-      application_ = OrthancStone::Application::Create(*context_, viewport);
+      application_ = OrthancStone::SingleFrameViewerApplication::Create(OrthancStone::StoneApplication::GetInstance(), viewport);
 
       {
         OrthancStone::WebGLViewportsRegistry::Accessor accessor(
--- a/Applications/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApplication.h	Tue Sep 29 17:52:35 2026 +0200
+++ b/Applications/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApplication.h	Wed Sep 30 11:50:48 2026 +0200
@@ -35,16 +35,16 @@
 
 namespace OrthancStone
 {
-  class Application : public ObserverBase<Application>
+  class SingleFrameViewerApplication : public ObserverBase<SingleFrameViewerApplication>
   {
   private:
-    ILoadersContext&                         context_;
+    StoneApplication::Context&               context_;
     boost::shared_ptr<IViewport>             viewport_;
     boost::shared_ptr<DicomResourcesLoader>  dicomLoader_;
     boost::shared_ptr<SeriesFramesLoader>    framesLoader_;
 
-    Application(ILoadersContext& context,
-                boost::shared_ptr<IViewport> viewport) : 
+    SingleFrameViewerApplication(StoneApplication::Context& context,
+                                 boost::shared_ptr<IViewport> viewport) : 
       context_(context),
       viewport_(viewport)
     {
@@ -78,11 +78,11 @@
       //message.GetResources()->GetResource(0).Print(stdout);
 
       {
-        std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
+        std::unique_ptr<OrthancStone::IEnvironment::ILock> lock(context_.GetEnvironment().AcquireLock());
         SeriesFramesLoader::Factory f(*message.GetResources());
 
-        framesLoader_ = boost::dynamic_pointer_cast<SeriesFramesLoader>(f.Create(*lock));
-        Register<SeriesFramesLoader::FrameLoadedMessage>(*framesLoader_, &Application::Handle);
+        framesLoader_ = boost::dynamic_pointer_cast<SeriesFramesLoader>(f.Create(context_));
+        Register<SeriesFramesLoader::FrameLoadedMessage>(*framesLoader_, &SingleFrameViewerApplication::Handle);
 
         assert(message.HasUserPayload());
         const Orthanc::SingleValueObject<unsigned int>& payload =
@@ -97,17 +97,14 @@
     }
 
   public:
-    static boost::shared_ptr<Application> Create(ILoadersContext& context,
-                                                 boost::shared_ptr<IViewport> viewport)
+    static boost::shared_ptr<SingleFrameViewerApplication> Create(StoneApplication::Context& context,
+                                                                  boost::shared_ptr<IViewport> viewport)
     {
-      boost::shared_ptr<Application> application(new Application(context, viewport));
+      boost::shared_ptr<SingleFrameViewerApplication> application(new SingleFrameViewerApplication(context, viewport));
 
-      {
-        std::unique_ptr<ILoadersContext::ILock> lock(context.Lock());
-        application->dicomLoader_ = DicomResourcesLoader::Create(*lock);
-      }
+      application->dicomLoader_ = DicomResourcesLoader::Create(context);
 
-      application->Register<DicomResourcesLoader::SuccessMessage>(*application->dicomLoader_, &Application::Handle);
+      application->Register<DicomResourcesLoader::SuccessMessage>(*application->dicomLoader_, &SingleFrameViewerApplication::Handle);
 
       return application;
     }
@@ -116,7 +113,7 @@
                           const std::string& instanceId,
                           unsigned int frame)
     {
-      std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
+      std::unique_ptr<OrthancStone::IEnvironment::ILock> lock(context_.GetEnvironment().AcquireLock());
 
       dicomLoader_->ScheduleLoadOrthancResource(
         boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), 
@@ -130,7 +127,7 @@
                            const std::string& sopInstanceUid,
                            unsigned int frame)
     {
-      std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
+      std::unique_ptr<OrthancStone::IEnvironment::ILock> lock(context_.GetEnvironment().AcquireLock());
 
       // We first must load the "/metadata" to know the number of frames
       dicomLoader_->ScheduleGetDicomWeb(
@@ -176,7 +173,7 @@
       Type_DicomWeb = 2
     };
 
-    ILoadersContext&                           context_;
+    StoneApplication::Context&                   context_;
     std::unique_ptr<IWebViewerLoadersObserver>   observer_;
     bool                                       loadThumbnails_;
     DicomSource                                source_;
@@ -188,7 +185,7 @@
     boost::shared_ptr<DicomResourcesLoader>    resourcesLoader_;
     boost::shared_ptr<SeriesThumbnailsLoader>  thumbnailsLoader_;
 
-    WebViewerLoaders(ILoadersContext& context,
+    WebViewerLoaders(StoneApplication::Context& context,
                      IWebViewerLoadersObserver* observer) :
       context_(context),
       observer_(observer),
@@ -303,7 +300,7 @@
     }
 
   public:
-    static boost::shared_ptr<WebViewerLoaders> Create(ILoadersContext& context,
+    static boost::shared_ptr<WebViewerLoaders> Create(StoneApplication::Context& context,
                                                       const DicomSource& source,
                                                       bool loadThumbnails,
                                                       IWebViewerLoadersObserver* observer)
@@ -312,35 +309,31 @@
       application->source_ = source;
       application->loadThumbnails_ = loadThumbnails;
 
-      {
-        std::unique_ptr<ILoadersContext::ILock> lock(context.Lock());
-
-        application->resourcesLoader_ = DicomResourcesLoader::Create(*lock);
+      application->resourcesLoader_ = DicomResourcesLoader::Create(context);
 
-        {
-          SeriesThumbnailsLoader::Factory f;
-          f.SetPriority(PRIORITY_THUMBNAILS);
-          application->thumbnailsLoader_ = boost::dynamic_pointer_cast<SeriesThumbnailsLoader>(f.Create(*lock));
-        }
+      {
+        SeriesThumbnailsLoader::Factory f;
+        f.SetPriority(PRIORITY_THUMBNAILS);
+        application->thumbnailsLoader_ = boost::dynamic_pointer_cast<SeriesThumbnailsLoader>(f.Create(context));
+      }
 
-        application->Register<OrthancRestApiCommand::SuccessMessage>(
-          lock->GetOracleObservable(), &WebViewerLoaders::HandleOrthancRestApi);
-
-        application->Register<DicomResourcesLoader::SuccessMessage>(
-          *application->resourcesLoader_, &WebViewerLoaders::HandleLoadedResources);
+      application->Register<OrthancRestApiCommand::SuccessMessage>(
+        context.GetOracleObservable(), &WebViewerLoaders::HandleOrthancRestApi);
 
-        application->Register<SeriesThumbnailsLoader::SuccessMessage>(
-          *application->thumbnailsLoader_, &WebViewerLoaders::HandleThumbnail);
+      application->Register<DicomResourcesLoader::SuccessMessage>(
+        *application->resourcesLoader_, &WebViewerLoaders::HandleLoadedResources);
 
-        lock->AddLoader(application);
-      }
+      application->Register<SeriesThumbnailsLoader::SuccessMessage>(
+        *application->thumbnailsLoader_, &WebViewerLoaders::HandleThumbnail);
+
+      context.AddLoader(application);
 
       return application;
     }
     
     void AddDicomAllSeries()
     {
-      std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
+      std::unique_ptr<OrthancStone::IEnvironment::ILock> lock(context_.GetEnvironment().AcquireLock());
 
       if (source_.IsDicomWeb())
       {
@@ -354,7 +347,7 @@
         std::unique_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
         command->SetMethod(Orthanc::HttpMethod_Get);
         command->SetUri("/series");
-        lock->Schedule(GetSharedObserver(), PRIORITY_ADD_RESOURCES, command.release());
+        context_.Schedule(GetSharedObserver(), PRIORITY_ADD_RESOURCES, command.release());
       }
       else
       {
@@ -375,7 +368,7 @@
           filter.SetValue(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, studyInstanceUid, false);
           
           {
-            std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());      
+            std::unique_ptr<OrthancStone::IEnvironment::ILock> lock(context_.GetEnvironment().AcquireLock());
             
             std::set<Orthanc::DicomTag> tags;
 
@@ -398,10 +391,7 @@
           body["Query"]["StudyInstanceUID"] = studyInstanceUid;
           command->SetBody(body);
 
-          {
-            std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());      
-            lock->Schedule(GetSharedObserver(), PRIORITY_ADD_RESOURCES, command.release());
-          }
+          context_.Schedule(GetSharedObserver(), PRIORITY_ADD_RESOURCES, command.release());
         }
         else
         {
@@ -415,7 +405,7 @@
     {
       std::set<Orthanc::DicomTag> tags;
 
-      std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());      
+      std::unique_ptr<OrthancStone::IEnvironment::ILock> lock(context_.GetEnvironment().AcquireLock());
 
       if (scheduledStudies_.find(studyInstanceUid) == scheduledStudies_.end())
       {
@@ -457,7 +447,7 @@
           body["Query"]["SeriesInstanceUID"] = seriesInstanceUid;
           command->SetBody(body);
 
-          lock->Schedule(GetSharedObserver(), PRIORITY_ADD_RESOURCES, command.release());
+          context_.Schedule(GetSharedObserver(), PRIORITY_ADD_RESOURCES, command.release());
         }
         else
         {
@@ -470,7 +460,7 @@
     {
       if (source_.IsOrthanc())
       {
-        std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());      
+        std::unique_ptr<OrthancStone::IEnvironment::ILock> lock(context_.GetEnvironment().AcquireLock());
         resourcesLoader_->ScheduleLoadOrthancResources(
           loadedSeries_, PRIORITY_ADD_RESOURCES, source_,
           Orthanc::ResourceType_Study, orthancId, Orthanc::ResourceType_Series,
@@ -487,7 +477,7 @@
     {
       if (source_.IsOrthanc())
       {
-        std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());      
+        std::unique_ptr<OrthancStone::IEnvironment::ILock> lock(context_.GetEnvironment().AcquireLock());
         resourcesLoader_->ScheduleLoadOrthancResource(
           loadedSeries_, PRIORITY_ADD_RESOURCES,
           source_, Orthanc::ResourceType_Series, orthancId,
--- a/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Tue Sep 29 17:52:35 2026 +0200
+++ b/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Wed Sep 30 11:50:48 2026 +0200
@@ -751,7 +751,7 @@
   };
   
 private:
-  OrthancStone::ILoadersContext&                           context_;
+  OrthancStone::StoneApplication::Context&                 context_;
   std::unique_ptr<IObserver>                               observer_;
   OrthancStone::DicomSource                                source_;
   size_t                                                   pending_;
@@ -768,7 +768,7 @@
   typedef std::map<std::string, boost::shared_ptr<OrthancStone::DicomStructuredReport> >  StructuredReports;
   StructuredReports structuredReports_;
 
-  explicit ResourcesLoader(OrthancStone::ILoadersContext& context,
+  explicit ResourcesLoader(OrthancStone::StoneApplication::Context& context,
                            const OrthancStone::DicomSource& source) :
     context_(context),
     source_(source),
@@ -908,8 +908,7 @@
           message.GetInstance(i).LookupStringValue(sopClassUid, Orthanc::DICOM_TAG_SOP_CLASS_UID, false) &&
           OrthancStone::IsStructuredReport(OrthancStone::StringToSopClassUid(sopClassUid)))
       {
-        std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(context_.Lock());
-        lock->Schedule(
+        context_.Schedule(
           GetSharedObserver(), PRIORITY_NORMAL, OrthancStone::ParseDicomFromWadoCommand::Create(
             source_, message.GetStudyInstanceUid(), message.GetSeriesInstanceUid(), sopInstanceUid,
             false /* no transcoding */, Orthanc::DicomTransferSyntax_LittleEndianExplicit /* dummy value */,
@@ -1106,12 +1105,9 @@
       const std::string uri = ("studies/" + studyInstanceUid + "/series/" + seriesInstanceUid +
                                "/instances/" + sopInstanceUid + "/frames/1/rendered");
 
-      {
-        std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(context_.Lock());
-        lock->Schedule(
-          GetSharedObserver(), PRIORITY_LOW + 2, source_.CreateDicomWebCommand(
-            uri, arguments, headers, new Orthanc::SingleValueObject<std::string>(virtualSeriesId)));
-      }
+      context_.Schedule(
+        GetSharedObserver(), PRIORITY_LOW + 2, source_.CreateDicomWebCommand(
+          uri, arguments, headers, new Orthanc::SingleValueObject<std::string>(virtualSeriesId)));
     }
   }
 
@@ -1132,14 +1128,14 @@
     skipSeriesFromModalities_ = skipSeriesFromModalities;
   }
 
-  static boost::shared_ptr<ResourcesLoader> Create(const OrthancStone::ILoadersContext::ILock& lock,
+  static boost::shared_ptr<ResourcesLoader> Create(OrthancStone::StoneApplication::Context& context,
                                                    const OrthancStone::DicomSource& source)
   {
-    boost::shared_ptr<ResourcesLoader> loader(new ResourcesLoader(lock.GetContext(), source));
-
-    loader->resourcesLoader_ = OrthancStone::DicomResourcesLoader::Create(lock);
-    loader->thumbnailsLoader_ = OrthancStone::SeriesThumbnailsLoader::Create(lock, PRIORITY_LOW);
-    loader->metadataLoader_ = OrthancStone::SeriesMetadataLoader::Create(lock);
+    boost::shared_ptr<ResourcesLoader> loader(new ResourcesLoader(context, source));
+
+    loader->resourcesLoader_ = OrthancStone::DicomResourcesLoader::Create(context);
+    loader->thumbnailsLoader_ = OrthancStone::SeriesThumbnailsLoader::Create(context, PRIORITY_LOW);
+    loader->metadataLoader_ = OrthancStone::SeriesMetadataLoader::Create(context);
     
     loader->Register<OrthancStone::DicomResourcesLoader::SuccessMessage>(
       *loader->resourcesLoader_, &ResourcesLoader::Handle);
@@ -1151,10 +1147,10 @@
       *loader->metadataLoader_, &ResourcesLoader::Handle);
 
     loader->Register<OrthancStone::ParseDicomSuccessMessage>(
-      lock.GetOracleObservable(), &ResourcesLoader::Handle);
+      context.GetOracleObservable(), &ResourcesLoader::Handle);
 
     loader->Register<OrthancStone::HttpCommand::SuccessMessage>(
-      lock.GetOracleObservable(), &ResourcesLoader::HandleInstanceThumbnail);
+      context.GetOracleObservable(), &ResourcesLoader::HandleInstanceThumbnail);
 
     return loader;
   }
@@ -1447,8 +1443,7 @@
             accessor.GetInstance(i).LookupStringValue(sopInstanceUid, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false) &&
             OrthancStone::StringToSopClassUid(sopClassUid) == OrthancStone::SopClassUid_EncapsulatedPdf)
         {
-          std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(context_.Lock());
-          lock->Schedule(
+          context_.Schedule(
             GetSharedObserver(), PRIORITY_NORMAL, OrthancStone::ParseDicomFromWadoCommand::Create(
               source_, studyInstanceUid, seriesInstanceUid, sopInstanceUid,
               false /* no transcoding */, Orthanc::DicomTransferSyntax_LittleEndianExplicit /* dummy value */,
@@ -2672,7 +2667,7 @@
   
 
   std::unique_ptr<IObserver>                   observer_;
-  OrthancStone::WebAssemblyLoadersContext&               context_;
+  OrthancStone::StoneApplication::Context&               context_;
   boost::shared_ptr<OrthancStone::WebAssemblyViewport>   viewport_;
   boost::shared_ptr<OrthancStone::DicomResourcesLoader> loader_;
   OrthancStone::DicomSource                    source_;
@@ -3114,8 +3109,7 @@
   {
     if (frames_.get() != NULL)
     {
-      std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(context_.Lock());
-      lock->Schedule(
+      context_.Schedule(
         GetSharedObserver(), priority, OrthancStone::ParseDicomFromWadoCommand::Create(
           source_, studyInstanceUid, seriesInstanceUid, sopInstanceUid, serverSideTranscoding_,
           Orthanc::DicomTransferSyntax_LittleEndianExplicit,
@@ -3157,7 +3151,7 @@
        * use the cached DICOM file.
        **/
       std::unique_ptr<OrthancStone::ParsedDicomCache::Accessor> accessor(
-        context_.GetCachedDicomInstance(instance.GetSopInstanceUid()));
+        dynamic_cast<OrthancStone::WebAssemblyOracle&>(context_.GetOracle()).GetCachedDicomInstance(instance.GetSopInstanceUid()));
 
       if (accessor.get() != NULL &&
           accessor->IsValid())
@@ -3210,10 +3204,7 @@
             GetSharedObserver(), instance.GetSopInstanceUid(), frameNumber,
             windowingTracker_.GetWindowing(), isMonochrome1, isPrefetch)));
 
-      {
-        std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(context_.Lock());
-        lock->Schedule(GetSharedObserver(), priority, command.release());
-      }
+      context_.Schedule(GetSharedObserver(), priority, command.release());
     }
   }
 
@@ -3235,7 +3226,7 @@
     }
   }
   
-  ViewerViewport(OrthancStone::WebAssemblyLoadersContext& context,
+  ViewerViewport(OrthancStone::StoneApplication::Context& context,
                  const OrthancStone::DicomSource& source,
                  const std::string& canvas,
                  boost::shared_ptr<FramesCache> cache,
@@ -3441,7 +3432,7 @@
     emscripten_set_wheel_callback(viewport_->GetCanvasCssSelector().c_str(), this, true, NULL);
   }
 
-  static boost::shared_ptr<ViewerViewport> Create(OrthancStone::WebAssemblyLoadersContext& context,
+  static boost::shared_ptr<ViewerViewport> Create(OrthancStone::StoneApplication::Context& context,
                                                   const OrthancStone::DicomSource& source,
                                                   const std::string& canvas,
                                                   boost::shared_ptr<FramesCache> framesCache,
@@ -3453,17 +3444,15 @@
       new ViewerViewport(context, source, canvas, framesCache, instancesCache, softwareRendering, linearInterpolation));
 
     {
-      std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(context.Lock());
-    
-      viewport->loader_ = OrthancStone::DicomResourcesLoader::Create(*lock);
+      viewport->loader_ = OrthancStone::DicomResourcesLoader::Create(context);
       viewport->Register<OrthancStone::DicomResourcesLoader::SuccessMessage>(
         *viewport->loader_, &ViewerViewport::Handle);
 
       viewport->Register<OrthancStone::HttpCommand::SuccessMessage>(
-        lock->GetOracleObservable(), &ViewerViewport::Handle);
+        context.GetOracleObservable(), &ViewerViewport::Handle);
 
       viewport->Register<OrthancStone::ParseDicomSuccessMessage>(
-        lock->GetOracleObservable(), &ViewerViewport::Handle);
+        context.GetOracleObservable(), &ViewerViewport::Handle);
 
       viewport->Register<OrthancStone::AnnotationsSceneLayer::AnnotationChangedMessage>(
         *viewport->stoneAnnotations_, &ViewerViewport::Handle);
@@ -4745,8 +4734,7 @@
 
   if (!resourcesLoader_)
   {
-    std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(context_->Lock());
-    resourcesLoader_ = ResourcesLoader::Create(*lock, source_);
+    resourcesLoader_ = ResourcesLoader::Create(OrthancStone::StoneApplication::GetInstance(), source_);
     resourcesLoader_->AcquireObserver(new WebAssemblyObserver);
   }
 
@@ -4769,7 +4757,8 @@
   if (found == allViewports_.end())
   {
     boost::shared_ptr<ViewerViewport> viewport(
-      ViewerViewport::Create(*context_, source_, canvas, framesCache_, instancesCache_, softwareRendering_, linearInterpolation_));
+      ViewerViewport::Create(OrthancStone::StoneApplication::GetInstance(),
+                             source_, canvas, framesCache_, instancesCache_, softwareRendering_, linearInterpolation_));
     viewport->SetMouseButtonActions(leftButtonAction_, middleButtonAction_, rightButtonAction_);
     viewport->AcquireObserver(new WebAssemblyObserver);
     viewport->AddLayerSource(*overlayLayerSource_);
--- a/OrthancStone/Sources/Loaders/DicomResourcesLoader.cpp	Tue Sep 29 17:52:35 2026 +0200
+++ b/OrthancStone/Sources/Loaders/DicomResourcesLoader.cpp	Wed Sep 30 11:50:48 2026 +0200
@@ -571,10 +571,7 @@
     command->AcquirePayload(new OrthancInstanceTagsHandler(shared_from_this(), target, priority,
                                                            source, remainingCommands, userPayload));
 
-    {
-      std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
-      lock->Schedule(GetSharedObserver(), priority, command.release());
-    }
+    context_.Schedule(GetSharedObserver(), priority, command.release());
   }
 
 
@@ -591,10 +588,7 @@
     command->AcquirePayload(new OrthancOneChildInstanceHandler(shared_from_this(), target, priority,
                                                                source, remainingCommands, userPayload));
 
-    {
-      std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
-      lock->Schedule(GetSharedObserver(), priority, command.release());
-    }
+    context_.Schedule(GetSharedObserver(), priority, command.release());
   }
     
     
@@ -612,9 +606,9 @@
   }
 
 
-  boost::shared_ptr<DicomResourcesLoader> DicomResourcesLoader::Create(const ILoadersContext::ILock& stone)
+  boost::shared_ptr<DicomResourcesLoader> DicomResourcesLoader::Create(StoneApplication::Context& stone)
   {
-    boost::shared_ptr<DicomResourcesLoader> result(new DicomResourcesLoader(stone.GetContext()));
+    boost::shared_ptr<DicomResourcesLoader> result(new DicomResourcesLoader(stone));
     result->Register<HttpCommand::SuccessMessage>(stone.GetOracleObservable(), &DicomResourcesLoader::Handle);
     result->Register<OracleCommandExceptionMessage>(stone.GetOracleObservable(), &DicomResourcesLoader::Handle);
     result->Register<OrthancRestApiCommand::SuccessMessage>(stone.GetOracleObservable(), &DicomResourcesLoader::Handle);
@@ -679,10 +673,7 @@
       source.CreateDicomWebCommand(uri, arguments, headers, 
                                    new DicomWebHandler(shared_from_this(), target, priority, source, protection)));
       
-    {
-      std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
-      lock->Schedule(GetSharedObserver(), priority, command.release());
-    }
+    context_.Schedule(GetSharedObserver(), priority, command.release());
   }
   
 
@@ -742,11 +733,7 @@
       source.CreateDicomWebCommand(uri, arguments, headers, 
                                    new DicomWebHandler(shared_from_this(), target, priority, source, protection)));
 
-
-    {
-      std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
-      lock->Schedule(GetSharedObserver(), priority, command.release());
-    }
+    context_.Schedule(GetSharedObserver(), priority, command.release());
   }
 
     
@@ -818,14 +805,10 @@
                               (shared_from_this(), target, priority, source,
                                remainingCommands, bottomLevel, protection));
 
-      {
-        std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
-
-        // GetSharedObserver() means "this" (for use as an IObserver), as a 
-        // shared_ptr
-        // The oracle will thus call "this" 
-        lock->Schedule(GetSharedObserver(), priority, command.release());
-      }
+      // GetSharedObserver() means "this" (for use as an IObserver), as a 
+      // shared_ptr
+      // The oracle will thus call "this" 
+      context_.Schedule(GetSharedObserver(), priority, command.release());
     }
   }
 
@@ -853,10 +836,7 @@
     std::unique_ptr<ReadFileCommand> command(new ReadFileCommand(path));
     command->AcquirePayload(new DicomDirHandler(shared_from_this(), target, priority, source, protection));
 
-    {
-      std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());      
-      lock->Schedule(GetSharedObserver(), priority, command.release());
-    }
+    context_.Schedule(GetSharedObserver(), priority, command.release());
 #else
     throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
                                     "DCMTK is disabled, cannot load DICOMDIR");
@@ -878,10 +858,7 @@
     command->SetPixelDataIncluded(includePixelData);
     command->AcquirePayload(new Handler(shared_from_this(), target, priority, source, protection));
 
-    {
-      std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
-      lock->Schedule(GetSharedObserver(), priority, command.release());
-    }
+    context_.Schedule(GetSharedObserver(), priority, command.release());
 #else
     throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
                                     "DCMTK is disabled, cannot load DICOM files");
--- a/OrthancStone/Sources/Loaders/DicomResourcesLoader.h	Tue Sep 29 17:52:35 2026 +0200
+++ b/OrthancStone/Sources/Loaders/DicomResourcesLoader.h	Wed Sep 30 11:50:48 2026 +0200
@@ -95,12 +95,12 @@
                                              boost::shared_ptr<unsigned int> remainingCommands,
                                              boost::shared_ptr<Orthanc::IDynamicObject> userPayload);
     
-    explicit DicomResourcesLoader(ILoadersContext& context) :
+    explicit DicomResourcesLoader(StoneApplication::Context& context) :
       context_(context)
     {
     }
 
-    ILoadersContext&  context_;
+    StoneApplication::Context&  context_;
 
 
   public:
@@ -155,14 +155,14 @@
     class Factory : public ILoaderFactory
     {
     public:
-      virtual boost::shared_ptr<IObserver> Create(ILoadersContext::ILock& stone) ORTHANC_OVERRIDE
+      virtual boost::shared_ptr<IObserver> Create(StoneApplication::Context& stone) ORTHANC_OVERRIDE
       {
         return DicomResourcesLoader::Create(stone);
       }
     };
 
 
-    static boost::shared_ptr<DicomResourcesLoader> Create(const ILoadersContext::ILock& stone);
+    static boost::shared_ptr<DicomResourcesLoader> Create(StoneApplication::Context& stone);
 
     void ScheduleGetDicomWeb(boost::shared_ptr<LoadedDicomResources> target,
                              int priority,
--- a/OrthancStone/Sources/Loaders/DicomVolumeLoader.cpp	Tue Sep 29 17:52:35 2026 +0200
+++ b/OrthancStone/Sources/Loaders/DicomVolumeLoader.cpp	Wed Sep 30 11:50:48 2026 +0200
@@ -152,7 +152,7 @@
   }
 
 
-  boost::shared_ptr<IObserver> DicomVolumeLoader::Factory::Create(ILoadersContext::ILock& context)
+  boost::shared_ptr<IObserver> DicomVolumeLoader::Factory::Create(StoneApplication::Context& context)
   { 
     boost::shared_ptr<SeriesFramesLoader> frames =
       boost::dynamic_pointer_cast<SeriesFramesLoader>(framesFactory_.Create(context));
--- a/OrthancStone/Sources/Loaders/DicomVolumeLoader.h	Tue Sep 29 17:52:35 2026 +0200
+++ b/OrthancStone/Sources/Loaders/DicomVolumeLoader.h	Wed Sep 30 11:50:48 2026 +0200
@@ -114,7 +114,7 @@
         framesFactory_.SetDicomDir(dicomDirPath, dicomDir);
       }
 
-      virtual boost::shared_ptr<IObserver> Create(ILoadersContext::ILock& context) ORTHANC_OVERRIDE;
+      virtual boost::shared_ptr<IObserver> Create(StoneApplication::Context& context) ORTHANC_OVERRIDE;
     };
 
     bool IsValid() const
--- a/OrthancStone/Sources/Loaders/ILoaderFactory.h	Tue Sep 29 17:52:35 2026 +0200
+++ b/OrthancStone/Sources/Loaders/ILoaderFactory.h	Wed Sep 30 11:50:48 2026 +0200
@@ -23,6 +23,7 @@
 
 #pragma once
 
+#include "../StoneApplication.h"
 #include "ILoadersContext.h"
 
 namespace OrthancStone
@@ -38,6 +39,6 @@
      * Factory function that creates a new loader, to be used by the
      * Stone loaders context.
      **/
-    virtual boost::shared_ptr<IObserver> Create(ILoadersContext::ILock& context) = 0;
+    virtual boost::shared_ptr<IObserver> Create(StoneApplication::Context& context) = 0;
   };
 }
--- a/OrthancStone/Sources/Loaders/SeriesFramesLoader.cpp	Tue Sep 29 17:52:35 2026 +0200
+++ b/OrthancStone/Sources/Loaders/SeriesFramesLoader.cpp	Wed Sep 30 11:50:48 2026 +0200
@@ -115,7 +115,7 @@
   };
     
 
-  SeriesFramesLoader::SeriesFramesLoader(ILoadersContext& context,
+  SeriesFramesLoader::SeriesFramesLoader(StoneApplication::Context& context,
                                          LoadedDicomResources& instances,
                                          const std::string& dicomDirPath,
                                          boost::shared_ptr<LoadedDicomResources> dicomDir) :
@@ -312,10 +312,10 @@
   }
 
 
-  boost::shared_ptr<IObserver> SeriesFramesLoader::Factory::Create(ILoadersContext::ILock& stone)
+  boost::shared_ptr<IObserver> SeriesFramesLoader::Factory::Create(StoneApplication::Context& stone)
   {
     boost::shared_ptr<SeriesFramesLoader> loader(
-      new SeriesFramesLoader(stone.GetContext(), instances_, dicomDirPath_, dicomDir_));
+      new SeriesFramesLoader(stone, instances_, dicomDirPath_, dicomDir_));
     loader->Register<GetOrthancImageCommand::SuccessMessage>(stone.GetOracleObservable(), &SeriesFramesLoader::Handle);
     loader->Register<GetOrthancWebViewerJpegCommand::SuccessMessage>(stone.GetOracleObservable(), &SeriesFramesLoader::Handle);
     loader->Register<HttpCommand::SuccessMessage>(stone.GetOracleObservable(), &SeriesFramesLoader::Handle);
@@ -371,10 +371,7 @@
         command->SetPixelDataIncluded(true);
         command->AcquirePayload(new Payload(source, index, sopInstanceUid, quality, protection.release()));
 
-        {
-          std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
-          lock->Schedule(GetSharedObserver(), priority, command.release());
-        }
+        context_.Schedule(GetSharedObserver(), priority, command.release());
       }
       else
       {
@@ -410,11 +407,8 @@
         std::unique_ptr<Payload> payload(new Payload(source, index, sopInstanceUid, quality, protection.release()));
         payload->SetWindowing(windowing);
 
-        {
-          std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
-          lock->Schedule(GetSharedObserver(), priority,
-                         source.CreateDicomWebCommand(uri + "/rendered", arguments, headers, payload.release()));
-        }
+        context_.Schedule(GetSharedObserver(), priority,
+                          source.CreateDicomWebCommand(uri + "/rendered", arguments, headers, payload.release()));
       }
       else
       {
@@ -432,10 +426,7 @@
                                         source.CreateDicomWebCommand(uri, empty, empty, NULL)));
         command->AcquirePayload(payload.release());
 
-        {
-          std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
-          lock->Schedule(GetSharedObserver(), priority, command.release());
-        }
+        context_.Schedule(GetSharedObserver(), priority, command.release());
 #else
         throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented,
                                         "DCMTK is not enabled, cannot parse a DICOM instance");
@@ -469,10 +460,7 @@
         command->SetExpectedPixelFormat(parameters.GetExpectedPixelFormat());
         command->AcquirePayload(new Payload(source, index, sopInstanceUid, quality, protection.release()));
 
-        {
-          std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
-          lock->Schedule(GetSharedObserver(), priority, command.release());
-        }
+        context_.Schedule(GetSharedObserver(), priority, command.release());
       }
       else if (quality == 0 && source.HasOrthancAdvancedPreview())
       {
@@ -491,10 +479,7 @@
         command->SetHttpHeader("Accept", Orthanc::MIME_PAM);
         command->AcquirePayload(new Payload(source, index, sopInstanceUid, quality, protection.release()));
 
-        {
-          std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
-          lock->Schedule(GetSharedObserver(), priority, command.release());
-        }
+        context_.Schedule(GetSharedObserver(), priority, command.release());
       }
     }
     else
--- a/OrthancStone/Sources/Loaders/SeriesFramesLoader.h	Tue Sep 29 17:52:35 2026 +0200
+++ b/OrthancStone/Sources/Loaders/SeriesFramesLoader.h	Wed Sep 30 11:50:48 2026 +0200
@@ -43,12 +43,12 @@
   private:
     class Payload;
 
-    ILoadersContext&                         context_;
+    StoneApplication::Context&               context_;
     SeriesOrderedFrames                      frames_;
     std::string                              dicomDirPath_;
     boost::shared_ptr<LoadedDicomResources>  dicomDir_;
 
-    SeriesFramesLoader(ILoadersContext& context,
+    SeriesFramesLoader(StoneApplication::Context& context,
                        LoadedDicomResources& instances,
                        const std::string& dicomDirPath,
                        boost::shared_ptr<LoadedDicomResources> dicomDir);
@@ -159,7 +159,7 @@
       void SetDicomDir(const std::string& dicomDirPath,
                        boost::shared_ptr<LoadedDicomResources> dicomDir);
 
-      virtual boost::shared_ptr<IObserver> Create(ILoadersContext::ILock& context) ORTHANC_OVERRIDE;
+      virtual boost::shared_ptr<IObserver> Create(StoneApplication::Context& context) ORTHANC_OVERRIDE;
     };
 
     const SeriesOrderedFrames& GetOrderedFrames() const
--- a/OrthancStone/Sources/Loaders/SeriesMetadataLoader.cpp	Tue Sep 29 17:52:35 2026 +0200
+++ b/OrthancStone/Sources/Loaders/SeriesMetadataLoader.cpp	Wed Sep 30 11:50:48 2026 +0200
@@ -189,7 +189,7 @@
   }
 
 
-  boost::shared_ptr<SeriesMetadataLoader> SeriesMetadataLoader::Create(const ILoadersContext::ILock& context)
+  boost::shared_ptr<SeriesMetadataLoader> SeriesMetadataLoader::Create(StoneApplication::Context& context)
   {
     boost::shared_ptr<DicomResourcesLoader> loader(DicomResourcesLoader::Create(context));
       
--- a/OrthancStone/Sources/Loaders/SeriesMetadataLoader.h	Tue Sep 29 17:52:35 2026 +0200
+++ b/OrthancStone/Sources/Loaders/SeriesMetadataLoader.h	Wed Sep 30 11:50:48 2026 +0200
@@ -130,14 +130,14 @@
     class Factory : public ILoaderFactory
     {
     public:
-      virtual boost::shared_ptr<IObserver> Create(ILoadersContext::ILock& context) ORTHANC_OVERRIDE
+      virtual boost::shared_ptr<IObserver> Create(StoneApplication::Context& context) ORTHANC_OVERRIDE
       {
         return SeriesMetadataLoader::Create(context);
       }
     };
 
 
-    static boost::shared_ptr<SeriesMetadataLoader> Create(const ILoadersContext::ILock& context);
+    static boost::shared_ptr<SeriesMetadataLoader> Create(StoneApplication::Context& context);
 
   
     class Accessor : public boost::noncopyable
--- a/OrthancStone/Sources/Loaders/SeriesThumbnailsLoader.cpp	Tue Sep 29 17:52:35 2026 +0200
+++ b/OrthancStone/Sources/Loaders/SeriesThumbnailsLoader.cpp	Wed Sep 30 11:50:48 2026 +0200
@@ -457,8 +457,7 @@
 
   void SeriesThumbnailsLoader::Schedule(IOracleCommand* command)
   {
-    std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
-    lock->Schedule(GetSharedObserver(), priority_, command);
+    context_.Schedule(GetSharedObserver(), priority_, command);
   }    
 
   
@@ -586,7 +585,7 @@
   }
 
 
-  SeriesThumbnailsLoader::SeriesThumbnailsLoader(ILoadersContext& context,
+  SeriesThumbnailsLoader::SeriesThumbnailsLoader(StoneApplication::Context& context,
                                                  int priority) :
     context_(context),
     priority_(priority),
@@ -596,11 +595,10 @@
   }
     
   
-  boost::shared_ptr<SeriesThumbnailsLoader> SeriesThumbnailsLoader::Create(
-    const ILoadersContext::ILock& stone,
-    int priority)
+  boost::shared_ptr<SeriesThumbnailsLoader> SeriesThumbnailsLoader::Create(StoneApplication::Context& stone,
+                                                                           int priority)
   {
-    boost::shared_ptr<SeriesThumbnailsLoader> result(new SeriesThumbnailsLoader(stone.GetContext(), priority));
+    boost::shared_ptr<SeriesThumbnailsLoader> result(new SeriesThumbnailsLoader(stone, priority));
     result->Register<GetOrthancImageCommand::SuccessMessage>(stone.GetOracleObservable(), &SeriesThumbnailsLoader::Handle);
     result->Register<HttpCommand::SuccessMessage>(stone.GetOracleObservable(), &SeriesThumbnailsLoader::Handle);
     result->Register<OracleCommandExceptionMessage>(stone.GetOracleObservable(), &SeriesThumbnailsLoader::Handle);
--- a/OrthancStone/Sources/Loaders/SeriesThumbnailsLoader.h	Tue Sep 29 17:52:35 2026 +0200
+++ b/OrthancStone/Sources/Loaders/SeriesThumbnailsLoader.h	Wed Sep 30 11:50:48 2026 +0200
@@ -148,7 +148,7 @@
     // Maps a "Series Instance UID" to a thumbnail
     typedef std::map<std::string, Thumbnail*>  Thumbnails;
 
-    ILoadersContext&       context_;
+    StoneApplication::Context&  context_;
     Thumbnails             thumbnails_;
     int                    priority_;
     unsigned int           width_;
@@ -174,7 +174,7 @@
 
     void Handle(const OracleCommandExceptionMessage& message);
 
-    SeriesThumbnailsLoader(ILoadersContext& context,
+    SeriesThumbnailsLoader(StoneApplication::Context& context,
                            int priority);
     
   public:
@@ -194,7 +194,7 @@
         priority_ = priority;
       }
 
-      virtual boost::shared_ptr<IObserver> Create(ILoadersContext::ILock& context)
+      virtual boost::shared_ptr<IObserver> Create(StoneApplication::Context& context)
       {
         return SeriesThumbnailsLoader::Create(context, priority_);
       }
@@ -207,7 +207,7 @@
     }
 
 
-    static boost::shared_ptr<SeriesThumbnailsLoader> Create(const ILoadersContext::ILock& context,
+    static boost::shared_ptr<SeriesThumbnailsLoader> Create(StoneApplication::Context& context,
                                                             int priority);
 
     void SetThumbnailSize(unsigned int width,
--- a/OrthancStone/Sources/StoneApplication.cpp	Tue Sep 29 17:52:35 2026 +0200
+++ b/OrthancStone/Sources/StoneApplication.cpp	Wed Sep 30 11:50:48 2026 +0200
@@ -23,6 +23,7 @@
 
 #include "StoneApplication.h"
 
+#include "Loaders/OracleScheduler.h"
 #include "StoneException.h"
 
 #include <Compatibility.h>
@@ -273,6 +274,8 @@
 #else
 #   error Support your platform here
 #endif
+
+    oracleScheduler_ = OracleScheduler::Create(*this, 1, 4, 1);  // TODO Refactoring - Parameters
   }
 
 
@@ -312,6 +315,26 @@
   }
 
 
+  void StoneApplication::Context::Schedule(boost::shared_ptr<IObserver> receiver,
+                                           int priority,
+                                           IOracleCommand* command /* Takes ownership */)
+  {
+    assert(oracleScheduler_);
+
+    {
+      std::unique_ptr<IEnvironment::ILock> lock(GetEnvironment().AcquireLock());
+      oracleScheduler_->Schedule(receiver, priority, command);
+    }
+  }
+
+
+  void StoneApplication::Context::AddLoader(const boost::shared_ptr<IObserver>& loader)
+  {
+    Orthanc::Mutex::ScopedLock lock(loadersMutex_);
+    loaders_.push_back(loader);
+  }
+
+
   bool StoneApplication::Run()
   {
     try
--- a/OrthancStone/Sources/StoneApplication.h	Tue Sep 29 17:52:35 2026 +0200
+++ b/OrthancStone/Sources/StoneApplication.h	Wed Sep 30 11:50:48 2026 +0200
@@ -31,9 +31,14 @@
 
 #include <WebServiceParameters.h>
 
+#include <MultiThreading/Mutex.h>  // TODO Refactoring - Remove this
+#include <list>  // TODO Refactoring - Remove this
+
 
 namespace OrthancStone
 {
+  class OracleScheduler;
+
   class StoneApplication : public boost::noncopyable
   {
   public:
@@ -113,6 +118,11 @@
       class PImpl;
       PImpl* pimpl_;
 
+      boost::shared_ptr<OracleScheduler>  oracleScheduler_;
+
+      Orthanc::Mutex   loadersMutex_;  // TODO Refactoring - Remove this
+      std::list< boost::shared_ptr<IObserver> >  loaders_;  // TODO Refactoring - Remove this
+
     public:
       Context(const Configuration& configuration);
 
@@ -126,6 +136,21 @@
                                const IMessage& message) ORTHANC_OVERRIDE;
 
       IObservable& GetOracleObservable();  // TODO Refactoring - Remove this
+
+      void Schedule(boost::shared_ptr<IObserver> receiver,
+                    int priority,
+                    IOracleCommand* command /* Takes ownership */);  // 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
+       * with the lifetime of the Stone context: This is useful if
+       * your Stone application does not keep a reference to the
+       * loader by itself (typically in global promises), which would
+       * make the loader disappear as soon as the scope of the
+       * variable is left.
+       **/
+      void AddLoader(const boost::shared_ptr<IObserver>& loader);  // TODO Refactoring - Remove this
     };