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();