diff options
| author | Alon Zakai <alonzakai@gmail.com> | 2012-10-22 15:30:03 -0700 | 
|---|---|---|
| committer | Alon Zakai <alonzakai@gmail.com> | 2012-10-22 15:30:03 -0700 | 
| commit | e48db465e2a2f13b2e9c4797d3334fc6d2b29b6d (patch) | |
| tree | 79937100322df639de1bc78b3f1d3dd25871650c | |
| parent | 11a4926fc6c2bfe43fef3c66ad30e4b2df612616 (diff) | |
initial work on worker api
| -rw-r--r-- | src/library_browser.js | 66 | ||||
| -rw-r--r-- | src/postamble.js | 26 | ||||
| -rw-r--r-- | src/settings.js | 7 | ||||
| -rw-r--r-- | system/include/emscripten/emscripten.h | 54 | ||||
| -rwxr-xr-x | tests/runner.py | 4 | ||||
| -rw-r--r-- | tests/worker_api_main.cpp | 24 | ||||
| -rw-r--r-- | tests/worker_api_worker.cpp | 16 | 
7 files changed, 194 insertions, 3 deletions
diff --git a/src/library_browser.js b/src/library_browser.js index 99106fc3..7e2866b8 100644 --- a/src/library_browser.js +++ b/src/library_browser.js @@ -42,6 +42,7 @@ mergeInto(LibraryManager.library, {      },      pointerLock: false,      moduleContextCreatedCallbacks: [], +    workers: [],      ensureObjects: function() {        if (Browser.ensured) return; @@ -565,6 +566,71 @@ mergeInto(LibraryManager.library, {      } else {        return Date.now();      } +  }, + +  emscripten_create_worker: function(url) { +    url = Pointer_stringify(url); +    var id = Browser.workers.length; +    var info = { +      worker: new Worker(url), +      callbacks: [], +      buffer: 0, +      bufferSize: 0 +    }; +    info.worker.onmessage = function(msg) { +      var info = Browser.workers[id]; +      if (!info) return; // worker was destroyed meanwhile +      var callbackId = msg.data.callbackId; +      var callbackInfo = info.callbacks[callbackId]; +      if (!callbackInfo) return; // no callback or callback removed meanwhile +      info.callbacks[callbackId] = null; // TODO: reuse callbackIds, compress this +      var data = msg.data.data; +      if (!data.byteLength) data = new Uint8Array(data); +      if (!info.buffer || info.bufferSize < data.length) { +        if (info.buffer) _free(info.buffer); +        info.bufferSize = data.length; +        info.buffer = _malloc(data.length); +      } +      HEAPU8.set(data, info.buffer); +      callbackInfo.func(info.buffer, data.length, callbackInfo.arg); +    }; +    Browser.workers.push(info); +    return id; +  }, + +  emscripten_destroy_worker: function(id) { +    var info = Browser.workers[id]; +    info.worker.terminate(); +    if (info.buffer) _free(info.buffer); +    Browser.workers[id] = null; +  }, + +  emscripten_call_worker: function(id, funcName, data, size, callback, arg) { +    funcName = Pointer_stringify(funcName); +    var info = Browser.workers[id]; +    var callbackId = -1; +    if (callback) { +      callbackId = info.callbacks.length; +      info.callbacks.push({ +        func: Runtime.getFuncWrapper(callback), +        arg: arg +      }); +    } +    info.worker.postMessage({ +      funcName: funcName, +      callbackId: callbackId, +      data: {{{ makeHEAPView('U8', 'data', 'data + size') }}} +    }); +  }, + +  emscripten_worker_respond: function(data, size) { +    if (!inWorkerCall) throw 'not in worker call!'; +    if (workerResponded) throw 'already responded!'; +    workerResponded = true; +    postMessage({ +      callbackId: workerCallbackId, +      data: {{{ makeHEAPView('U8', 'data', 'data + size') }}} +    });    }  }); diff --git a/src/postamble.js b/src/postamble.js index d164f049..86c990e8 100644 --- a/src/postamble.js +++ b/src/postamble.js @@ -113,3 +113,29 @@ if (shouldRunNow) {  // {{POST_RUN_ADDITIONS}} +#if BUILD_AS_WORKER + +var buffer = 0, bufferSize = 0; +var inWorkerCall = false, workerResponded = false, workerCallbackId = -1; + +onmessage = function(msg) { +  var func = Module['_' + msg.data.funcName]; +  if (!func) throw 'invalid worker function to call: ' + msg.data.funcName; +  var data = msg.data.data; +  if (!data.byteLength) data = new Uint8Array(data); +  if (!buffer || bufferSize < data.length) { +    if (buffer) _free(buffer); +    bufferSize = data.length; +    buffer = _malloc(data.length); +  } +  HEAPU8.set(data, buffer); + +  inWorkerCall = true; +  workerResponded = false; +  workerCallbackId = msg.data.callbackId; +  func(buffer, data.length); +  inWorkerCall = false; +} + +#endif + diff --git a/src/settings.js b/src/settings.js index a6c11448..a1c41de7 100644 --- a/src/settings.js +++ b/src/settings.js @@ -213,10 +213,9 @@ var SHOW_LABELS = 0; // Show labels in the generated code  var PRINT_SPLIT_FILE_MARKER = 0; // Prints markers in Javascript generation to split the file later on. See emcc --split option. -var BUILD_AS_SHARED_LIB = 0; // Whether to build the code as a shared library, which -                             // must be loaded dynamically using dlopen(). +var BUILD_AS_SHARED_LIB = 0; // Whether to build the code as a shared library                               // 0 here means this is not a shared lib: It is a main file. -                             // 1 means this is a normal shared lib. +                             // 1 means this is a normal shared lib, load it with dlopen().                               // 2 means this is a shared lib that will be linked at runtime,                               //   which means it will insert its functions into                               //   the global namespace. See STATIC_LIBS_TO_LOAD. @@ -225,6 +224,8 @@ var RUNTIME_LINKED_LIBS = []; // If this is a main file (BUILD_AS_SHARED_LIB ==                                // BUILD_AS_SHARED_LIB == 2.                                // NOTE: LLVM optimizations run separately on the main file and                                //       linked libraries can break things. +var BUILD_AS_WORKER = 0; // If set to 1, this is a worker library, a special kind of library +                         // that is run in a worker. See emscripten.h  var LINKABLE = 0; // If set to 1, this file can be linked with others, either as a shared                    // library or as the main file that calls a shared library. To enable that,                    // we will not internalize all symbols and cull the unused ones, in other diff --git a/system/include/emscripten/emscripten.h b/system/include/emscripten/emscripten.h index ed078acd..2bdee946 100644 --- a/system/include/emscripten/emscripten.h +++ b/system/include/emscripten/emscripten.h @@ -213,6 +213,60 @@ int emscripten_async_prepare(const char* file, void (*onload)(const char*), void  void emscripten_async_prepare_data(char* data, int size, const char *suffix, void *arg, void (*onload)(void*, const char*), void (*onerror)(void*));  /* + * Worker API. Basically a wrapper around web workers, lets + * you create workers and communicate with them. + + * Note that the current API is mainly focused on a main thread that + * sends jobs to workers and waits for responses, i.e., in an + * asymmetrical manner, there is no current API to send a message + * without being asked for it from a worker to the main thread. + * + */ + +typedef int worker_t; + +/* + * Create and destroy workers. + */ +worker_t emscripten_create_worker(const char *url); +void emscripten_destroy_worker(worker_t worker); + +/* + * Asynchronously call a worker. + * + * The worker function will be called with two parameters: a + * data pointer, and a size.  The data block defined by the + * pointer and size exists only during the callback and + * _cannot_ be relied upon afterwards - if you need to keep some + * of that information around, you need to copy it to a safe + * location. + * + * The called worker function can return data, by calling + * emscripten_worker_respond(). If called, and if a callback was + * given, then the callback will be called with three arguments: + * a data pointer, a size, and  * an argument that was provided + * when calling emscripten_call_worker (to more easily associate + * callbacks to calls). The data block defined by the data pointer + * and size behave like the data block in the worker function - + * it exists only during the callback. + * + * @funcname the name of the function in the worker. The function + *           must be a C function (so no C++ name mangling), and + *           must be exported (EXPORTED_FUNCTIONS). + * @data the address of a block of memory to copy over + * @size the size of the block of memory + * @callback the callback with the response (can be null) + * @arg an argument to be passed to the callback + */ +void emscripten_call_worker(worker_t worker, const char *funcname, char *data, int size, void (*callback)(char *, int, void*), void *arg); + +/* + * Sends a response when in a worker call. Should only be + * called once in each call. + */ +void emscripten_worker_respond(char *data, int size); + +/*   * Profiling tools.   * INIT must be called first, with the maximum identifier that   * will be used. BEGIN will add some code that marks diff --git a/tests/runner.py b/tests/runner.py index 41b9ee19..cb788dbb 100755 --- a/tests/runner.py +++ b/tests/runner.py @@ -9114,6 +9114,10 @@ elif 'browser' in str(sys.argv):        ''')        self.btest('pre_run_deps.cpp', expected='10', args=['--pre-js', 'pre.js']) +    def test_worker_api(self): +      Popen(['python', EMCC, path_from_root('tests', 'worker_api_worker.cpp'), '-o', 'worker.js', '-s', 'BUILD_AS_WORKER=1', '-O0', '--closure', '0', '-s', 'EXPORTED_FUNCTIONS=["_one", "_two"]']).communicate() +      self.btest('worker_api_main.cpp', args=['-O0', '--closure', '0'], expected='566') +      pids_to_clean = []      def clean_pids(self):        return diff --git a/tests/worker_api_main.cpp b/tests/worker_api_main.cpp new file mode 100644 index 00000000..f91102b2 --- /dev/null +++ b/tests/worker_api_main.cpp @@ -0,0 +1,24 @@ +#include <stdio.h> +#include <assert.h> +#include <emscripten.h> + +int w1; + +void c1(char *data, int size, void *arg) { +  assert((int)arg == 93); +  int *x = (int*)data; +  printf("c1: %d,%d\n", x[0], x[1]); + +  int result = x[1] % x[0]; +  REPORT_RESULT(); +} + +int main() { +  w1 = emscripten_create_worker("worker.js"); + +  int x[2] = { 100, 6002 }; +  emscripten_call_worker(w1, "one", (char*)x, sizeof(x), c1, (void*)93); + +  return 0; +} + diff --git a/tests/worker_api_worker.cpp b/tests/worker_api_worker.cpp new file mode 100644 index 00000000..9a671a91 --- /dev/null +++ b/tests/worker_api_worker.cpp @@ -0,0 +1,16 @@ +#include <assert.h> +#include <emscripten.h> + +extern "C" { + +void one(char *data, int size) { +  int *x = (int*)data; +  int num = size/sizeof(int); +  for (int i = 0; i < num; i++) { +    x[i] += 1234; +  } +  emscripten_worker_respond(data, size); +} + +} +  | 
