Verwendung des synchronen virtuellen XHR-gestützten Dateisystems

Emscripten unterstützt das verzögerte Laden von Binärdaten von HTTP-Servern mittels XHR. Diese Funktionalität kann verwendet werden, um ein Backend für den synchronen Dateizugriff aus kompiliertem Code zu erstellen.

Das Backend kann die Startzeit verbessern, da das gesamte Dateisystem nicht vorgeladen werden muss, bevor der kompilierte Code ausgeführt wird. Es kann auch sehr effizient sein, wenn der Webserver Byte-Serving unterstützt — in diesem Fall kann Emscripten nur die Teile von Dateien lesen, die tatsächlich benötigt werden.

Warnung

Dieser Mechanismus ist nur in Web Workern möglich (aufgrund von Browsereinschränkungen).

Hinweis

Wenn Byte-Serving nicht unterstützt wird, muss Emscripten die gesamte Datei (egal wie groß) laden, selbst wenn nur ein einziges Byte gelesen wird.

Testcode

Ein Beispiel für die Implementierung eines synchronen virtuellen XHR-gestützten Dateisystems finden Sie im Testcode unter test/test_browser.py (siehe test_chunked_synchronous_xhr). Der Testfall enthält auch einen HTTP-Server (siehe test_chunked_synchronous_xhr_server), der CORS-Header zeigt, die möglicherweise gesetzt werden müssen (wenn die Ressourcen von derselben Domäne gehostet werden, von der Emscripten ausgeführt wird, gibt es kein Problem).

Die Tests verwenden checksummer.c als das von Emscripten kompilierte Programm. Dies ist einfach ein einfaches C-Programm, das synchrone libc-Dateisystemaufrufe wie fopen(), fread(), fclose() usw. verwendet.

JavaScript-Code wird hinzugefügt (unter Verwendung der pre-js-Option von emcc), um die Dateisystemaufrufe in checksummer.c einer Datei im virtuellen Dateisystem zuzuordnen. Diese Datei wird frühzeitig bei der Emscripten-Initialisierung mit FS.createLazyFile() erstellt, aber erst dann mit Inhalt vom Server geladen, wenn die Datei zum ersten Mal von kompiliertem Code aufgerufen wird. Der hinzugefügte JavaScript-Code richtet auch die Kommunikation zwischen dem Web Worker und dem Haupt-Thread ein.

Anweisungen

  1. Sie müssen dem generierten Code JavaScript hinzufügen, um die von Ihrem kompilierten nativen Code und dem Server aufgerufene Datei zuzuordnen.

    Der Testcode erstellt einfach eine Datei im virtuellen Dateisystem mit FS.createLazyFile() und weist den kompilierten Code an, dieselbe Datei (/bigfile) zu verwenden.

    , r"""
          Module.arguments = ["/bigfile"];
          Module.preInit = () => {
            FS.createLazyFile('/', "bigfile", "https://:11111/bogus_file_path", true, false);
          };
          
    

    Hinweis

    • Der kompilierte Testcode (in diesem Fall) erhält den Dateinamen aus Kommandozeilenargumenten — diese werden in Emscripten mit Module.arguments festgelegt.

    • Der Aufruf zum Erstellen der Datei wird zu Module.preInit hinzugefügt. Dies stellt sicher, dass er vor jedem kompilierten Code ausgeführt wird.

    • Das zusätzliche JavaScript wird mit der prejs-Option von emcc hinzugefügt.

  2. Der hinzugefügte JavaScript-Code sollte auch Code enthalten, der es dem Web Worker ermöglicht, mit dem ursprünglichen Thread zu kommunizieren.

    Der Testcode fügt zu diesem Zweck das folgende JavaScript zum Web Worker hinzu. Er verwendet postMessage(), um sein stdout an den Haupt-Thread zurückzusenden.

    
          Module.print = (s) => self.postMessage({channel: "stdout", line: s});
          Module.printErr = (s) => { self.postMessage({channel: "stderr", char: s, trace: ((doTrace && s === 10) ? new Error().stack : null)}); doTrace = false; };
        
    

    Hinweis

    Wenn Sie die oben genannte Lösung verwenden, sollte die übergeordnete Seite wahrscheinlich handgeschriebenen Glue-Code enthalten, um die stdout-Daten zu verarbeiten.

  3. Sie benötigen eine Seite, die den Web Worker startet.

    Der Testcode, der dies tut, wird unten gezeigt.

     '''
          <html>
          <body>
            Worker Test
            <script>
              var worker = new Worker('worker.js');
              worker.onmessage = async (event) => {
                await fetch('https://:%s/report_result?' + event.data);
                window.close();
              };
            </script>
          </body>
          </html>
        ''' % self.port)
    
        for file_data in (1, 0):
          cmd = [EMCC, test_file('hello_world_worker.cpp'), '-o', 'worker.js'] + self.get_emcc_args()
          if file_data:
            cmd += ['--preload-file', 'file.dat']
          self.run_process(cmd)
          self.assertExists('worker.js')
          self.run_browser('main.html', '/report_result?hello from worker, and :' + ('data for w' if file_data else '') + ':')
    
        # code should run standalone too
        # To great memories >4gb we need the canary version of node
        if self.is_4gb():
          self.require_node_canary()
        self.assertContained('you should not see this text when in a worker!', self.run_js('worker.js'))
    
      @no_wasmfs('https://github.com/emscripten-core/emscripten/issues/19608')
      def test_mmap_lazyfile(self):
        create_file('lazydata.dat', 'hello world')
        create_file('pre.js', '''
          Module["preInit"] = () => {
            FS.createLazyFile('/', "lazy.txt", "lazydata.dat", true, false);
          }
        ''')
        self.emcc_args += ['--pre-js=pre.js', '--proxy-to-worker']
        self.btest_exit('test_mmap_lazyfile.c')
    
      @no_wasmfs('https://github.com/emscripten-core/emscripten/issues/19608')
      @no_firefox('keeps sending OPTIONS requests, and eventually errors')
      def test_chunked_synchronous_xhr(self):
        main = 'chunked_sync_xhr.html'
        worker_filename = "download_and_checksum_worker.js"
    
        create_file(main, r"""
          <!doctype html>
          <html>
          <head><meta charset="utf-8"><title>Chunked XHR</title></head>
          <body>
            Chunked XHR Web Worker Test
            <script>
              var worker = new Worker("%s");
              var buffer = [];
              worker.onmessage = async (event) => {
                if (event.data.channel === "stdout") {
                  await fetch('https://:%s/report_result?' + event.data.line);
                  window.close();
                } else {
                  if (event.data.trace) event.data.trace.split("\n").map(function(v) { console.error(v); });
                  if (event.data.line) {
                    console.error(event.data.line);
                  } else {
                    var v = event.data.char;
                    if (v == 10) {
                      var line = buffer.splice(0);
                      console.error(line = line.map(function(charCode){return String.fromCharCode(charCode);}).join(''));
                    } else {
                      buffer.push(v);
                    }
                  }
                }
              };
            </script>
          </body>
          </html>