Mercurial > hg > orthanc-stone
comparison Samples/WebAssembly/BasicMPR.cpp @ 825:9a6c7a5dcb76
reorganization
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 29 May 2019 15:45:15 +0200 |
parents | 15d493101c1e |
children | 2de01660debe |
comparison
equal
deleted
inserted
replaced
824:15d493101c1e | 825:9a6c7a5dcb76 |
---|---|
25 #include <emscripten/html5.h> | 25 #include <emscripten/html5.h> |
26 | 26 |
27 #include "../../Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h" | 27 #include "../../Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h" |
28 #include "../../Framework/OpenGL/WebAssemblyOpenGLContext.h" | 28 #include "../../Framework/OpenGL/WebAssemblyOpenGLContext.h" |
29 #include "../../Framework/Oracle/SleepOracleCommand.h" | 29 #include "../../Framework/Oracle/SleepOracleCommand.h" |
30 #include "../../Framework/Oracle/WebAssemblyOracle.h" | |
30 #include "../../Framework/Scene2D/GrayscaleStyleConfigurator.h" | 31 #include "../../Framework/Scene2D/GrayscaleStyleConfigurator.h" |
31 #include "../../Framework/Scene2D/OpenGLCompositor.h" | 32 #include "../../Framework/Scene2D/OpenGLCompositor.h" |
32 #include "../../Framework/Scene2D/PanSceneTracker.h" | 33 #include "../../Framework/Scene2D/PanSceneTracker.h" |
33 #include "../../Framework/Scene2D/RotateSceneTracker.h" | 34 #include "../../Framework/Scene2D/RotateSceneTracker.h" |
34 #include "../../Framework/Scene2D/ZoomSceneTracker.h" | 35 #include "../../Framework/Scene2D/ZoomSceneTracker.h" |
390 Refresh(); | 391 Refresh(); |
391 } | 392 } |
392 } | 393 } |
393 } | 394 } |
394 }; | 395 }; |
395 | |
396 | |
397 | |
398 | |
399 class WebAssemblyOracle : | |
400 public IOracle, | |
401 public IObservable | |
402 { | |
403 private: | |
404 typedef std::map<std::string, std::string> HttpHeaders; | |
405 | |
406 class Emitter : public IMessageEmitter | |
407 { | |
408 private: | |
409 WebAssemblyOracle& oracle_; | |
410 | |
411 public: | |
412 Emitter(WebAssemblyOracle& oracle) : | |
413 oracle_(oracle) | |
414 { | |
415 } | |
416 | |
417 virtual void EmitMessage(const IObserver& receiver, | |
418 const IMessage& message) | |
419 { | |
420 oracle_.EmitMessage(receiver, message); | |
421 } | |
422 }; | |
423 | |
424 | |
425 class FetchContext : public boost::noncopyable | |
426 { | |
427 private: | |
428 Emitter emitter_; | |
429 const IObserver& receiver_; | |
430 std::auto_ptr<IOracleCommand> command_; | |
431 std::string expectedContentType_; | |
432 | |
433 public: | |
434 FetchContext(WebAssemblyOracle& oracle, | |
435 const IObserver& receiver, | |
436 IOracleCommand* command, | |
437 const std::string& expectedContentType) : | |
438 emitter_(oracle), | |
439 receiver_(receiver), | |
440 command_(command), | |
441 expectedContentType_(expectedContentType) | |
442 { | |
443 if (command == NULL) | |
444 { | |
445 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
446 } | |
447 } | |
448 | |
449 const std::string& GetExpectedContentType() const | |
450 { | |
451 return expectedContentType_; | |
452 } | |
453 | |
454 void EmitMessage(const IMessage& message) | |
455 { | |
456 emitter_.EmitMessage(receiver_, message); | |
457 } | |
458 | |
459 IMessageEmitter& GetEmitter() | |
460 { | |
461 return emitter_; | |
462 } | |
463 | |
464 const IObserver& GetReceiver() const | |
465 { | |
466 return receiver_; | |
467 } | |
468 | |
469 IOracleCommand& GetCommand() const | |
470 { | |
471 return *command_; | |
472 } | |
473 | |
474 template <typename T> | |
475 const T& GetTypedCommand() const | |
476 { | |
477 return dynamic_cast<T&>(*command_); | |
478 } | |
479 }; | |
480 | |
481 static void FetchSucceeded(emscripten_fetch_t *fetch) | |
482 { | |
483 /** | |
484 * Firstly, make a local copy of the fetched information, and | |
485 * free data associated with the fetch. | |
486 **/ | |
487 | |
488 std::auto_ptr<FetchContext> context(reinterpret_cast<FetchContext*>(fetch->userData)); | |
489 | |
490 std::string answer; | |
491 if (fetch->numBytes > 0) | |
492 { | |
493 answer.assign(fetch->data, fetch->numBytes); | |
494 } | |
495 | |
496 /** | |
497 * TODO - HACK - As of emscripten-1.38.31, the fetch API does | |
498 * not contain a way to retrieve the HTTP headers of the | |
499 * answer. We make the assumption that the "Content-Type" header | |
500 * of the response is the same as the "Accept" header of the | |
501 * query. This should be fixed in future versions of emscripten. | |
502 * https://github.com/emscripten-core/emscripten/pull/8486 | |
503 **/ | |
504 | |
505 HttpHeaders headers; | |
506 if (!context->GetExpectedContentType().empty()) | |
507 { | |
508 headers["Content-Type"] = context->GetExpectedContentType(); | |
509 } | |
510 | |
511 | |
512 emscripten_fetch_close(fetch); | |
513 | |
514 | |
515 /** | |
516 * Secondly, use the retrieved data. | |
517 **/ | |
518 | |
519 try | |
520 { | |
521 if (context.get() == NULL) | |
522 { | |
523 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
524 } | |
525 else | |
526 { | |
527 switch (context->GetCommand().GetType()) | |
528 { | |
529 case IOracleCommand::Type_OrthancRestApi: | |
530 { | |
531 OrthancRestApiCommand::SuccessMessage message | |
532 (context->GetTypedCommand<OrthancRestApiCommand>(), headers, answer); | |
533 context->EmitMessage(message); | |
534 break; | |
535 } | |
536 | |
537 case IOracleCommand::Type_GetOrthancImage: | |
538 { | |
539 context->GetTypedCommand<GetOrthancImageCommand>().ProcessHttpAnswer | |
540 (context->GetEmitter(), context->GetReceiver(), answer, headers); | |
541 break; | |
542 } | |
543 | |
544 case IOracleCommand::Type_GetOrthancWebViewerJpeg: | |
545 { | |
546 context->GetTypedCommand<GetOrthancWebViewerJpegCommand>().ProcessHttpAnswer | |
547 (context->GetEmitter(), context->GetReceiver(), answer); | |
548 break; | |
549 } | |
550 | |
551 default: | |
552 LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle: " | |
553 << context->GetCommand().GetType(); | |
554 } | |
555 } | |
556 } | |
557 catch (Orthanc::OrthancException& e) | |
558 { | |
559 LOG(ERROR) << "Error while processing a fetch answer in the oracle: " << e.What(); | |
560 } | |
561 } | |
562 | |
563 static void FetchFailed(emscripten_fetch_t *fetch) | |
564 { | |
565 std::auto_ptr<FetchContext> context(reinterpret_cast<FetchContext*>(fetch->userData)); | |
566 | |
567 LOG(ERROR) << "Fetching " << fetch->url << " failed, HTTP failure status code: " << fetch->status; | |
568 | |
569 /** | |
570 * TODO - The following code leads to an infinite recursion, at | |
571 * least with Firefox running on incognito mode => WHY? | |
572 **/ | |
573 //emscripten_fetch_close(fetch); // Also free data on failure. | |
574 } | |
575 | |
576 | |
577 class FetchCommand : public boost::noncopyable | |
578 { | |
579 private: | |
580 WebAssemblyOracle& oracle_; | |
581 const IObserver& receiver_; | |
582 std::auto_ptr<IOracleCommand> command_; | |
583 Orthanc::HttpMethod method_; | |
584 std::string uri_; | |
585 std::string body_; | |
586 HttpHeaders headers_; | |
587 unsigned int timeout_; | |
588 std::string expectedContentType_; | |
589 | |
590 public: | |
591 FetchCommand(WebAssemblyOracle& oracle, | |
592 const IObserver& receiver, | |
593 IOracleCommand* command) : | |
594 oracle_(oracle), | |
595 receiver_(receiver), | |
596 command_(command), | |
597 method_(Orthanc::HttpMethod_Get), | |
598 timeout_(0) | |
599 { | |
600 if (command == NULL) | |
601 { | |
602 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
603 } | |
604 } | |
605 | |
606 void SetMethod(Orthanc::HttpMethod method) | |
607 { | |
608 method_ = method; | |
609 } | |
610 | |
611 void SetUri(const std::string& uri) | |
612 { | |
613 uri_ = uri; | |
614 } | |
615 | |
616 void SetBody(std::string& body /* will be swapped */) | |
617 { | |
618 body_.swap(body); | |
619 } | |
620 | |
621 void SetHttpHeaders(const HttpHeaders& headers) | |
622 { | |
623 headers_ = headers; | |
624 } | |
625 | |
626 void SetTimeout(unsigned int timeout) | |
627 { | |
628 timeout_ = timeout; | |
629 } | |
630 | |
631 void Execute() | |
632 { | |
633 if (command_.get() == NULL) | |
634 { | |
635 // Cannot call Execute() twice | |
636 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
637 } | |
638 | |
639 emscripten_fetch_attr_t attr; | |
640 emscripten_fetch_attr_init(&attr); | |
641 | |
642 const char* method; | |
643 | |
644 switch (method_) | |
645 { | |
646 case Orthanc::HttpMethod_Get: | |
647 method = "GET"; | |
648 break; | |
649 | |
650 case Orthanc::HttpMethod_Post: | |
651 method = "POST"; | |
652 break; | |
653 | |
654 case Orthanc::HttpMethod_Delete: | |
655 method = "DELETE"; | |
656 break; | |
657 | |
658 case Orthanc::HttpMethod_Put: | |
659 method = "PUT"; | |
660 break; | |
661 | |
662 default: | |
663 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
664 } | |
665 | |
666 strcpy(attr.requestMethod, method); | |
667 | |
668 attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; | |
669 attr.onsuccess = FetchSucceeded; | |
670 attr.onerror = FetchFailed; | |
671 attr.timeoutMSecs = timeout_ * 1000; | |
672 | |
673 std::vector<const char*> headers; | |
674 headers.reserve(2 * headers_.size() + 1); | |
675 | |
676 std::string expectedContentType; | |
677 | |
678 for (HttpHeaders::const_iterator it = headers_.begin(); it != headers_.end(); ++it) | |
679 { | |
680 std::string key; | |
681 Orthanc::Toolbox::ToLowerCase(key, it->first); | |
682 | |
683 if (key == "accept") | |
684 { | |
685 expectedContentType = it->second; | |
686 } | |
687 | |
688 if (key != "accept-encoding") // Web browsers forbid the modification of this HTTP header | |
689 { | |
690 headers.push_back(it->first.c_str()); | |
691 headers.push_back(it->second.c_str()); | |
692 } | |
693 } | |
694 | |
695 headers.push_back(NULL); // Termination of the array of HTTP headers | |
696 | |
697 attr.requestHeaders = &headers[0]; | |
698 | |
699 if (!body_.empty()) | |
700 { | |
701 attr.requestDataSize = body_.size(); | |
702 attr.requestData = body_.c_str(); | |
703 } | |
704 | |
705 // Must be the last call to prevent memory leak on error | |
706 attr.userData = new FetchContext(oracle_, receiver_, command_.release(), expectedContentType); | |
707 emscripten_fetch(&attr, uri_.c_str()); | |
708 } | |
709 }; | |
710 | |
711 | |
712 void Execute(const IObserver& receiver, | |
713 OrthancRestApiCommand* command) | |
714 { | |
715 FetchCommand fetch(*this, receiver, command); | |
716 | |
717 fetch.SetMethod(command->GetMethod()); | |
718 fetch.SetUri(command->GetUri()); | |
719 fetch.SetHttpHeaders(command->GetHttpHeaders()); | |
720 fetch.SetTimeout(command->GetTimeout()); | |
721 | |
722 if (command->GetMethod() == Orthanc::HttpMethod_Put || | |
723 command->GetMethod() == Orthanc::HttpMethod_Put) | |
724 { | |
725 std::string body; | |
726 command->SwapBody(body); | |
727 fetch.SetBody(body); | |
728 } | |
729 | |
730 fetch.Execute(); | |
731 } | |
732 | |
733 | |
734 void Execute(const IObserver& receiver, | |
735 GetOrthancImageCommand* command) | |
736 { | |
737 FetchCommand fetch(*this, receiver, command); | |
738 | |
739 fetch.SetUri(command->GetUri()); | |
740 fetch.SetHttpHeaders(command->GetHttpHeaders()); | |
741 fetch.SetTimeout(command->GetTimeout()); | |
742 | |
743 fetch.Execute(); | |
744 } | |
745 | |
746 | |
747 void Execute(const IObserver& receiver, | |
748 GetOrthancWebViewerJpegCommand* command) | |
749 { | |
750 FetchCommand fetch(*this, receiver, command); | |
751 | |
752 fetch.SetUri(command->GetUri()); | |
753 fetch.SetHttpHeaders(command->GetHttpHeaders()); | |
754 fetch.SetTimeout(command->GetTimeout()); | |
755 | |
756 fetch.Execute(); | |
757 } | |
758 | |
759 | |
760 | |
761 class TimeoutContext | |
762 { | |
763 private: | |
764 WebAssemblyOracle& oracle_; | |
765 const IObserver& receiver_; | |
766 std::auto_ptr<SleepOracleCommand> command_; | |
767 | |
768 public: | |
769 TimeoutContext(WebAssemblyOracle& oracle, | |
770 const IObserver& receiver, | |
771 IOracleCommand* command) : | |
772 oracle_(oracle), | |
773 receiver_(receiver) | |
774 { | |
775 if (command == NULL) | |
776 { | |
777 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
778 } | |
779 else | |
780 { | |
781 command_.reset(dynamic_cast<SleepOracleCommand*>(command)); | |
782 } | |
783 } | |
784 | |
785 void EmitMessage() | |
786 { | |
787 SleepOracleCommand::TimeoutMessage message(*command_); | |
788 oracle_.EmitMessage(receiver_, message); | |
789 } | |
790 }; | |
791 | |
792 | |
793 static void TimeoutCallback(void *userData) | |
794 { | |
795 std::auto_ptr<TimeoutContext> context(reinterpret_cast<TimeoutContext*>(userData)); | |
796 context->EmitMessage(); | |
797 } | |
798 | |
799 | |
800 | |
801 public: | |
802 WebAssemblyOracle(MessageBroker& broker) : | |
803 IObservable(broker) | |
804 { | |
805 } | |
806 | |
807 virtual void Schedule(const IObserver& receiver, | |
808 IOracleCommand* command) | |
809 { | |
810 std::auto_ptr<IOracleCommand> protection(command); | |
811 | |
812 if (command == NULL) | |
813 { | |
814 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
815 } | |
816 | |
817 switch (command->GetType()) | |
818 { | |
819 case IOracleCommand::Type_OrthancRestApi: | |
820 Execute(receiver, dynamic_cast<OrthancRestApiCommand*>(protection.release())); | |
821 break; | |
822 | |
823 case IOracleCommand::Type_GetOrthancImage: | |
824 Execute(receiver, dynamic_cast<GetOrthancImageCommand*>(protection.release())); | |
825 break; | |
826 | |
827 case IOracleCommand::Type_GetOrthancWebViewerJpeg: | |
828 Execute(receiver, dynamic_cast<GetOrthancWebViewerJpegCommand*>(protection.release())); | |
829 break; | |
830 | |
831 case IOracleCommand::Type_Sleep: | |
832 { | |
833 unsigned int timeoutMS = dynamic_cast<SleepOracleCommand*>(command)->GetDelay(); | |
834 emscripten_set_timeout(TimeoutCallback, timeoutMS, new TimeoutContext(*this, receiver, protection.release())); | |
835 break; | |
836 } | |
837 | |
838 default: | |
839 LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle: " << command->GetType(); | |
840 } | |
841 } | |
842 | |
843 virtual void Start() | |
844 { | |
845 } | |
846 | |
847 virtual void Stop() | |
848 { | |
849 } | |
850 }; | |
851 } | 396 } |
852 | 397 |
853 | 398 |
854 | 399 |
855 | 400 |
1006 LOG(INFO) << "STARTING"; | 551 LOG(INFO) << "STARTING"; |
1007 Schedule(); | 552 Schedule(); |
1008 } | 553 } |
1009 }; | 554 }; |
1010 | 555 |
1011 //static TestSleep testSleep(broker_, oracle_); | 556 static TestSleep testSleep(broker_, oracle_); |
1012 } | 557 } |
1013 | 558 |
1014 | 559 |
1015 extern "C" | 560 extern "C" |
1016 { | 561 { |
1050 emscripten_set_keydown_callback("#window", NULL, false, OnKey); | 595 emscripten_set_keydown_callback("#window", NULL, false, OnKey); |
1051 emscripten_set_keyup_callback("#window", NULL, false, OnKey); | 596 emscripten_set_keyup_callback("#window", NULL, false, OnKey); |
1052 | 597 |
1053 emscripten_request_animation_frame_loop(OnAnimationFrame, NULL); | 598 emscripten_request_animation_frame_loop(OnAnimationFrame, NULL); |
1054 | 599 |
1055 oracle_.Start(); | |
1056 loader_->LoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"); | 600 loader_->LoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"); |
1057 } | 601 } |
1058 catch (Orthanc::OrthancException& e) | 602 catch (Orthanc::OrthancException& e) |
1059 { | 603 { |
1060 LOG(ERROR) << "Exception during Initialize(): " << e.What(); | 604 LOG(ERROR) << "Exception during Initialize(): " << e.What(); |