diff options
-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); +} + +} + |