aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/library_browser.js66
-rw-r--r--src/postamble.js26
-rw-r--r--src/settings.js7
-rw-r--r--system/include/emscripten/emscripten.h54
-rwxr-xr-xtests/runner.py4
-rw-r--r--tests/worker_api_main.cpp24
-rw-r--r--tests/worker_api_worker.cpp16
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);
+}
+
+}
+