view Deprecated/Platforms/Wasm/Defaults.cpp @ 1491:c5627b8493f3

merge
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 24 Jun 2020 17:17:24 +0200
parents fbc5bfde6c95
children
line wrap: on
line source

/**
 * Stone of Orthanc
 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 * Department, University Hospital of Liege, Belgium
 * Copyright (C) 2017-2020 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 "Defaults.h"

#include "WasmWebService.h"
#include "WasmDelayedCallExecutor.h"
#include "../../../Framework/Deprecated/Widgets/TestCairoWidget.h"
#include "../../../Framework/Deprecated/Viewport/WidgetViewport.h"
#include <Applications/Wasm/StartupParametersBuilder.h>
#include <Platforms/Wasm/WasmPlatformApplicationAdapter.h>
#include <Framework/StoneInitialization.h>
#include <Core/Logging.h>
#include <sstream>

#include <algorithm>


static unsigned int width_ = 0;
static unsigned int height_ = 0;

/**********************************/

static std::unique_ptr<OrthancStone::IStoneApplication> application;
static std::unique_ptr<OrthancStone::WasmPlatformApplicationAdapter> applicationWasmAdapter = NULL;
static std::unique_ptr<OrthancStone::StoneApplicationContext> context;
static OrthancStone::StartupParametersBuilder startupParametersBuilder;
static OrthancStone::MessageBroker broker;

static OrthancStone::ViewportContentChangedObserver viewportContentChangedObserver_(broker);
static OrthancStone::StatusBar statusBar_;

static std::list<std::shared_ptr<Deprecated::WidgetViewport>> viewports_;

std::shared_ptr<Deprecated::WidgetViewport> FindViewportSharedPtr(ViewportHandle viewport) {
  for (const auto& v : viewports_) {
    if (v.get() == viewport) {
      return v;
    }
  }
  assert(false);
  return std::shared_ptr<Deprecated::WidgetViewport>();
}

#ifdef __cplusplus
extern "C" {
#endif

#if 0
  // rewrite malloc/free in order to monitor allocations.  We actually only monitor large allocations (like images ...)

  size_t bigChunksTotalSize = 0;
  std::map<void*, size_t> allocatedBigChunks;

  extern void* emscripten_builtin_malloc(size_t bytes);
  extern void emscripten_builtin_free(void* mem);

  void * __attribute__((noinline)) malloc(size_t size)
  {
    void *ptr = emscripten_builtin_malloc(size);
    if (size > 100000)
    {
      bigChunksTotalSize += size;
      printf("++ Allocated %zu bytes, got %p. (%zu MB consumed by big chunks)\n", size, ptr, bigChunksTotalSize/(1024*1024));
      allocatedBigChunks[ptr] = size;
    }
    return ptr;
  }

  void __attribute__((noinline)) free(void *ptr)
  {
    emscripten_builtin_free(ptr);

    std::map<void*, size_t>::iterator it = allocatedBigChunks.find(ptr);
    if (it != allocatedBigChunks.end())
    {
      bigChunksTotalSize -= it->second;
      printf("--     Freed %zu bytes at %p.   (%zu MB consumed by big chunks)\n", it->second, ptr, bigChunksTotalSize/(1024*1024));
      allocatedBigChunks.erase(it);
    }
  }
#endif // 0

  using namespace OrthancStone;

  // when WASM needs a C++ viewport
  ViewportHandle EMSCRIPTEN_KEEPALIVE CreateCppViewport() {
    
    std::shared_ptr<Deprecated::WidgetViewport> viewport(new Deprecated::WidgetViewport(broker));
    printf("viewport %x\n", (int)viewport.get());

    viewports_.push_back(viewport);

    printf("There are now %lu viewports in C++\n", viewports_.size());

    viewport->SetStatusBar(statusBar_);

    viewport->RegisterObserverCallback(
      new Callable<ViewportContentChangedObserver, Deprecated::IViewport::ViewportChangedMessage>
      (viewportContentChangedObserver_, &ViewportContentChangedObserver::OnViewportChanged));

    return viewport.get();
  }

  // when WASM does not need a viewport anymore, it should release it 
  void EMSCRIPTEN_KEEPALIVE ReleaseCppViewport(ViewportHandle viewport) {
    viewports_.remove_if([viewport](const std::shared_ptr<Deprecated::WidgetViewport>& v) { return v.get() == viewport;});

    printf("There are now %lu viewports in C++\n", viewports_.size());
  }

  void EMSCRIPTEN_KEEPALIVE CreateWasmApplication(ViewportHandle viewport) {
    printf("Initializing Stone\n");
    OrthancStone::StoneInitialize();
    printf("CreateWasmApplication\n");

    application.reset(CreateUserApplication(broker));
    applicationWasmAdapter.reset(CreateWasmApplicationAdapter(broker, application.get())); 
    Deprecated::WasmWebService::SetBroker(broker);
    Deprecated::WasmDelayedCallExecutor::SetBroker(broker);

    startupParametersBuilder.Clear();
  }

  void EMSCRIPTEN_KEEPALIVE SetStartupParameter(const char* keyc,
                                                  const char* value) {
    startupParametersBuilder.SetStartupParameter(keyc, value);
  }

  void EMSCRIPTEN_KEEPALIVE StartWasmApplication(const char* baseUri) {

    printf("StartWasmApplication\n");

    Orthanc::Logging::SetErrorWarnInfoTraceLoggingFunctions(
      stone_console_error, stone_console_warning,
      stone_console_info, stone_console_trace);

    // recreate a command line from uri arguments and parse it
    boost::program_options::variables_map parameters;
    boost::program_options::options_description options;
    application->DeclareStartupOptions(options);
    startupParametersBuilder.GetStartupParameters(parameters, options);

    context.reset(new OrthancStone::StoneApplicationContext(broker));
    context->SetOrthancBaseUrl(baseUri);
    printf("Base URL to Orthanc API: [%s]\n", baseUri);
    context->SetWebService(Deprecated::WasmWebService::GetInstance());
    context->SetDelayedCallExecutor(Deprecated::WasmDelayedCallExecutor::GetInstance());
    application->Initialize(context.get(), statusBar_, parameters);
    application->InitializeWasm();

//    viewport->SetSize(width_, height_);
    printf("StartWasmApplication - completed\n");
  }
  
  bool EMSCRIPTEN_KEEPALIVE WasmIsTraceLevelEnabled()
  {
    return Orthanc::Logging::IsTraceLevelEnabled();
  }

  bool EMSCRIPTEN_KEEPALIVE WasmIsInfoLevelEnabled()
  {
    return Orthanc::Logging::IsInfoLevelEnabled();
  }
  
  void EMSCRIPTEN_KEEPALIVE WasmDoAnimation()
  {
    for (auto viewport : viewports_) {
      // TODO Only launch the JavaScript timer if "HasAnimation()"
      if (viewport->HasAnimation())
      {
        viewport->DoAnimation();
      }

    }

  }
  

  void EMSCRIPTEN_KEEPALIVE ViewportSetSize(ViewportHandle viewport, unsigned int width, unsigned int height)
  {
    width_ = width;
    height_ = height;
    
    viewport->SetSize(width, height);
  }

  int EMSCRIPTEN_KEEPALIVE ViewportRender(ViewportHandle viewport,
                                          unsigned int width,
                                          unsigned int height,
                                          uint8_t* data)
  {
    viewportContentChangedObserver_.Reset();

    //printf("ViewportRender called %dx%d\n", width, height);
    if (width == 0 ||
        height == 0)
    {
      return 1;
    }

    Orthanc::ImageAccessor surface;
    surface.AssignWritable(Orthanc::PixelFormat_BGRA32, width, height, 4 * width, data);

    viewport->Render(surface);

    // Convert from BGRA32 memory layout (only color mode supported by
    // Cairo, which corresponds to CAIRO_FORMAT_ARGB32) to RGBA32 (as
    // expected by HTML5 canvas). This simply amounts to swapping the
    // B and R channels.
    uint8_t* p = data;
    for (unsigned int y = 0; y < height; y++) {
      for (unsigned int x = 0; x < width; x++) {
        uint8_t tmp = p[0];
        p[0] = p[2];
        p[2] = tmp;
        
        p += 4;
      }
    }

    return 1;
  }


  void EMSCRIPTEN_KEEPALIVE ViewportMouseDown(ViewportHandle viewport,
                                              unsigned int rawButton,
                                              int x,
                                              int y,
                                              unsigned int rawModifiers)
  {
    OrthancStone::MouseButton button;
    switch (rawButton)
    {
      case 0:
        button = OrthancStone::MouseButton_Left;
        break;

      case 1:
        button = OrthancStone::MouseButton_Middle;
        break;

      case 2:
        button = OrthancStone::MouseButton_Right;
        break;

      default:
        return;  // Unknown button
    }

    viewport->MouseDown(button, x, y, OrthancStone::KeyboardModifiers_None, std::vector<Deprecated::Touch>());
  }
  

  void EMSCRIPTEN_KEEPALIVE ViewportMouseWheel(ViewportHandle viewport,
                                               int deltaY,
                                               int x,
                                               int y,
                                               int isControl)
  {
    if (deltaY != 0)
    {
      OrthancStone::MouseWheelDirection direction = (deltaY < 0 ?
                                                     OrthancStone::MouseWheelDirection_Up :
                                                     OrthancStone::MouseWheelDirection_Down);
      OrthancStone::KeyboardModifiers modifiers = OrthancStone::KeyboardModifiers_None;

      if (isControl != 0)
      {
        modifiers = OrthancStone::KeyboardModifiers_Control;
      }

      viewport->MouseWheel(direction, x, y, modifiers);
    }
  }
  

  void EMSCRIPTEN_KEEPALIVE ViewportMouseMove(ViewportHandle viewport,
                                              int x,
                                              int y)
  {
    viewport->MouseMove(x, y, std::vector<Deprecated::Touch>());
  }

  void GetTouchVector(std::vector<Deprecated::Touch>& output,
                      int touchCount,
                      float x0,
                      float y0,
                      float x1,
                      float y1,
                      float x2,
                      float y2)
  {
    // TODO: it might be nice to try to pass all the x0,y0 coordinates as arrays but that's not so easy to pass array between JS and C++
    if (touchCount > 0)
    {
      output.push_back(Deprecated::Touch(x0, y0));
    }
    if (touchCount > 1)
    {
      output.push_back(Deprecated::Touch(x1, y1));
    }
    if (touchCount > 2)
    {
      output.push_back(Deprecated::Touch(x2, y2));
    }

  }

  void EMSCRIPTEN_KEEPALIVE ViewportTouchStart(ViewportHandle viewport,
                                              int touchCount,
                                              float x0,
                                              float y0,
                                              float x1,
                                              float y1,
                                              float x2,
                                              float y2)
  {
    // printf("touch start with %d touches\n", touchCount);

    std::vector<Deprecated::Touch> touches;
    GetTouchVector(touches, touchCount, x0, y0, x1, y1, x2, y2);
    viewport->TouchStart(touches);
  }

  void EMSCRIPTEN_KEEPALIVE ViewportTouchMove(ViewportHandle viewport,
                                              int touchCount,
                                              float x0,
                                              float y0,
                                              float x1,
                                              float y1,
                                              float x2,
                                              float y2)
  {
    // printf("touch move with %d touches\n", touchCount);

    std::vector<Deprecated::Touch> touches;
    GetTouchVector(touches, touchCount, x0, y0, x1, y1, x2, y2);
    viewport->TouchMove(touches);
  }

  void EMSCRIPTEN_KEEPALIVE ViewportTouchEnd(ViewportHandle viewport,
                                              int touchCount,
                                              float x0,
                                              float y0,
                                              float x1,
                                              float y1,
                                              float x2,
                                              float y2)
  {
    // printf("touch end with %d touches remaining\n", touchCount);

    std::vector<Deprecated::Touch> touches;
    GetTouchVector(touches, touchCount, x0, y0, x1, y1, x2, y2);
    viewport->TouchEnd(touches);
  }

  void EMSCRIPTEN_KEEPALIVE ViewportKeyPressed(ViewportHandle viewport,
                                               int key,
                                               const char* keyChar, 
                                               bool isShiftPressed, 
                                               bool isControlPressed,
                                               bool isAltPressed)
                                               
  {
    OrthancStone::KeyboardModifiers modifiers = OrthancStone::KeyboardModifiers_None;
    if (isShiftPressed) {
      modifiers = static_cast<OrthancStone::KeyboardModifiers>(modifiers + OrthancStone::KeyboardModifiers_Shift);
    }
    if (isControlPressed) {
      modifiers = static_cast<OrthancStone::KeyboardModifiers>(modifiers + OrthancStone::KeyboardModifiers_Control);
    }
    if (isAltPressed) {
      modifiers = static_cast<OrthancStone::KeyboardModifiers>(modifiers + OrthancStone::KeyboardModifiers_Alt);
    }

    char c = 0;
    if (keyChar != NULL && key == OrthancStone::KeyboardKeys_Generic) {
      c = keyChar[0];
    }
    viewport->KeyPressed(static_cast<OrthancStone::KeyboardKeys>(key), c, modifiers);
  }
  

  void EMSCRIPTEN_KEEPALIVE ViewportMouseUp(ViewportHandle viewport)
  {
    viewport->MouseUp();
  }
  

  void EMSCRIPTEN_KEEPALIVE ViewportMouseEnter(ViewportHandle viewport)
  {
    viewport->MouseEnter();
  }
  

  void EMSCRIPTEN_KEEPALIVE ViewportMouseLeave(ViewportHandle viewport)
  {
    viewport->MouseLeave();
  }

  const char* EMSCRIPTEN_KEEPALIVE SendSerializedMessageToStoneApplication(const char* message) 
  {
    static std::string output; // we don't want the string to be deallocated when we return to JS code so we always use the same string (this is fine since JS is single-thread)

    //printf("SendSerializedMessageToStoneApplication\n");
    //printf("%s", message);

    if (applicationWasmAdapter.get() != NULL) {
      applicationWasmAdapter->HandleSerializedMessageFromWeb(output, std::string(message));
      return output.c_str();
    }
    printf("This Stone application does not have a Web Adapter, unable to send messages");
    return NULL;
  }

#ifdef __cplusplus
}
#endif