Skip to content

Instantly share code, notes, and snippets.

@jcubic
Last active June 28, 2022 16:36
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jcubic/87f2b4c5ef567be43796e179ca08c0da to your computer and use it in GitHub Desktop.
Save jcubic/87f2b4c5ef567be43796e179ca08c0da to your computer and use it in GitHub Desktop.
POC for Emscripten Async code + jQuery Terminal
emcc -o main.html -s FETCH=1 -s NO_EXIT_RUNTIME=0 main.c
#include <stdio.h>
#include <string.h>
#include <emscripten/fetch.h>
int i = 0;
void read_async();
void read_success(emscripten_fetch_t *fetch) {
printf("You typed: %s\n", fetch->data);
emscripten_fetch_close(fetch);
if (strlen(fetch->data) > 0 || i++ == 5) {
read_async();
}
}
void read_fail(emscripten_fetch_t *fetch) {
emscripten_fetch_close(fetch);
}
void read_async() {
emscripten_fetch_attr_t attr;
emscripten_fetch_attr_init(&attr);
strcpy(attr.requestMethod, "GET");
attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY;
attr.onsuccess = read_success;
attr.onerror = read_fail;
emscripten_fetch(&attr, "___terminal::read");
}
int main() {
read_async();
}
<!DOCTYPE html>
<html>
<head>
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="https://cdn.rawgit.com/jcubic/jquery.terminal/master/js/jquery.terminal.min.js"></script>
<link href="https://cdn.rawgit.com/jcubic/jquery.terminal/master/css/jquery.terminal.min.css" rel="stylesheet"/>
<script src="https://unpkg.com/xhr-mock/dist/xhr-mock.js"></script>
</head>
<body>
</body>
<script>
var term = $('body').terminal();
var re = /^___terminal::/;
// XHR proxy that handle methods from fetch in C
window.XMLHttpRequest = (function(xhr) {
return function() {
var url;
var props = {
readyState: 4,
status: 200
};
var enc = new TextEncoder("utf-8");
return new Proxy(new xhr(), {
get: function(target, name) {
if (url && ['response', 'responseText', 'status', 'readyState'].indexOf(name) != -1) {
if (name == 'response') {
var response = enc.encode(props.responseText);
console.log(response);
return response;
}
return props[name];
} else if (name == 'open') {
return function(method, open_url) {
if (open_url.match(re)) {
url = open_url;
} else {
return target[name].apply(target, arguments);
}
};
} else if (name == 'send') {
return function(data) {
if (url) {
var payload = url.split('::');
if (payload[1] == 'read') {
term.read(
payload.length > 2 ? payload[2] : '',
function(text) {
props.responseText = text;
target.onload();
}
);
}
} else {
return target[name].apply(target, arguments);
}
};
}
return target[name];
},
set: function(target, name, value) {
target[name] = value;
}
});
};
})(window.XMLHttpRequest);
// below code modified from emscripten output html
var Module = {
preRun: [],
postRun: [],
print: function(text) {
console.log(text);
term.echo(text);
},
printErr: function(text) {
if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
term.error(text);
},
canvas: (function() {
var canvas = document.createElement('canvas');
// As a default initial behavior, pop up an alert when webgl context is lost. To make your
// application robust, you may want to override this behavior before shipping!
// See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
canvas.addEventListener("webglcontextlost", function(e) { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false);
return canvas;
})(),
setStatus: function(text) {
},
totalDependencies: 0,
monitorRunDependencies: function(left) {
this.totalDependencies = Math.max(this.totalDependencies, left);
Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.');
}
};
Module.setStatus('Downloading...');
window.onerror = function(event) {
// TODO: do not warn on ok events like simulating an infinite loop or exitStatus
Module.setStatus('Exception thrown, see JavaScript console');
spinnerElement.style.display = 'none';
Module.setStatus = function(text) {
if (text) Module.printErr('[post-exception status] ' + text);
};
};
</script>
<script async type="text/javascript" src="test.js"></script>
</html>
@jcubic
Copy link
Author

jcubic commented Jun 18, 2019

This is hack over Emscripten fetch function that use proxy over XHR (Emscripten don't give any way for async JS code see my issue from 2018 Async comunication with javascript). This is done so you can have C code that call read and it's calling read on terminal not using browser prompt. The whole code can be abstracted away for the user and pack into nice little lib (e.g. in function term_read). Same as with fetch printf also can use terminal so you have real terminal for Emscripten apps (you can use emscripten_fetch(&attr, "___terminal::print"); it can be wrapped in nice term_printf function).

This code was solution to this StackOverflow Question How can I run an interactive program compiled with Emscripten in a web page?

The code like content of StackOverflow is released with CC-BY-SA license.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment