同期仮想XHRバックエンドファイルシステムの使用

Emscriptenは、XHRを使用して、HTTPサーバーからのバイナリデータの遅延読み込みをサポートしています。この機能を使用して、コンパイル済みコードからの同期ファイルアクセスのためのバックエンドを作成できます。

バックエンドを使用することで、コンパイル済みコードの実行前にファイルシステム全体を事前にロードする必要がないため、起動時間を改善できます。また、Webサーバーがバイトサービングをサポートしている場合、Emscriptenは実際に必要なファイルの部分のみを読み取ることができるため、非常に効率的です。

警告

このメカニズムは、(ブラウザの制限のため)Web Workersでのみ可能です。

注記

バイトサービングがサポートされていない場合、Emscriptenは、1バイトでも読み取られる場合、(どれだけ大きくても)ファイル全体をロードする必要があります。

テストコード

同期仮想XHRバックエンドファイルシステムを実装する方法の例は、test/test_browser.pyのテストコード(test_chunked_synchronous_xhrを参照)に記載されています。このテストケースには、HTTPサーバー(test_chunked_synchronous_xhr_serverを参照)も含まれており、設定が必要となる可能性のあるCORSヘッダーを示しています(リソースがEmscriptenが実行されているのと同じドメインからホストされている場合、問題は発生しません)。

テストでは、Emscriptenでコンパイルされたプログラムとしてchecksummer.cを使用しています。これは、fopen()fread()fclose()などの同期libcファイルシステム呼び出しを使用する、単純な標準的なCプログラムです。

JavaScriptコードは(emccpre-jsオプションを使用して)追加され、**checksummer.c**のファイルシステム呼び出しを仮想ファイルシステム内のファイルにマッピングします。FS.createLazyFile()を使用してEmscriptenの初期化の早い段階でファイルが作成されますが、コンパイル済みコードによってファイルが最初にアクセスされたときにのみ、サーバーからのコンテンツでロードされます。追加されたJavaScriptコードは、Webワーカーとメインスレッド間の通信も設定します。

手順

  1. コンパイルされたネイティブコードとサーバーによってアクセスされるファイルをマッピングするには、生成されたコードにJavaScriptを追加する必要があります。

    テストコードは、FS.createLazyFile()を使用して仮想ファイルシステムにファイルを作成し、コンパイル済みコードが同じファイル(/bigfile)を使用するように設定します。

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

    注記

    • コンパイルされたテストコード(この場合)は、コマンドライン引数からファイル名を取得します。これは、Module.argumentsを使用してEmscriptenで設定されます。

    • ファイルを作成するための呼び出しは、Module.preInitに追加されます。これにより、コンパイル済みコードの前に実行されます。

    • 追加のJavaScriptは、emccprejsオプションを使用して追加されます。

  2. 追加されたJavaScriptには、Webワーカーが元のスレッドと通信できるようにするコードも含まれている必要があります。

    テストコードは、この目的のために、次のJavaScriptをWebワーカーに追加します。 postMessage()を使用して、そのstdoutをメインスレッドに送信します。

    
          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; };
        
    

    注記

    上記のソリューションを使用する場合、親ページには、stdoutデータを処理するための手書きのグルーコードが含まれている必要があります。

  3. Webワーカーを生成するページが必要です。

    これを行うテストコードを以下に示します。

     '''
          <html>
          <body>
            Worker Test
            <script>
              var worker = new Worker('worker.js');
              worker.onmessage = async (event) => {
                await fetch('http://localhost:%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('http://localhost:%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>