From a native app to a full JS app using Emscripten
About me
Florian Rival
App developer @
Developer of
and
@florianrival
github.com/4ian
what we want to do
Desktop, C++ powered app
Full JS app running
in browsers
WTF?
JAVASCRIPT AND HYBRID APPROACHES
-
Embed Javascript
-
Compile to Javascript
Mobile: Cordova, React Native...
Game engines: Cocos2d-JS...
Native APIs are often made available through a bridge.
Built-in feature of some languages (Haxe, CoffeeScript, ClosureScript...)
Separate tool/compiler for other languages
how emscripten works
- Compiles your C or C++ code using LLVM+Clang
$ emcc main.cpp -o output.js
- The intermediate representation (~assembly like) is then converted to asm.js (a subset of JS) instead of machine code
- Functions or classes are exposed using bindings.
- The generated code is working on a single, (very) large array representing the C++ program memory.
var HEAP = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
porting the codebase of gdevelop
Base classes (Project, scene, objects...)
Backend (Node.js + Express)
GUI & dialogs
libGD.js
Webapp (Angular.js/Pixi.js/React)
Filesystem
Game to code generation
Extensions
(new objects...)
exposing existing code
class Project : public ClassWithObjects {
public:
void setName(const std::string & name_) { name = name_; };
const std::string & getName() { return name; };
// ...
}
$ path/to/emscripten/tools/webidl_binder.py Bindings.idl .
$ emcc main.cpp glue.cpp -o output.js --post-js glue.js
interface Project {
void SetName([Const] DOMString name);
[Const, Ref] DOMString GetName();
}
-
take unmodified, existing code
-
write bindings in "web idl"
-
add the generated glue code
a few apis/libs are provided by emscripten
exclude or abstract the rest
- File system calls: abstracted in gd::AbstractFileSystem, then reimplemented using Node.js fs API.
exposing existing code
- GUI mostly rewritten in the WebApp using Angular.js, React and Pixi.js.
- Program output is piped to the console
- In-memory file system.
- A few more advanced libraries are available, like OpenGL (mapped to WebGL).
using the generated code
const project = new gd.Project();
project.setName("My new game");
console.log(project.getName());
// "My new game"
everything exposed is in module.*
classes are instanciated like usual js objects
//I wrapped Module into gd
gd.Project; //A C++ class
gd.sayHello("world"); //A function taking an argument
using the generated code
const obj = new gd.Parent();
const obj2 = new gd.Child();
obj.f();
obj2.f(); // Works
gd.expectAParent(obj2); //Works
types are converted TRANSPARENTLY
inheritance works as expected
//bool myFunction(gd::Project & p, std::string str, double d);
const str = "A JS string";
const p = new gd.Project();
gd.myFunction(p, str, 3); //
using emscripten for your project
beware of memory management
app.post('/my-express-route', (req, res) => {
const project = new gd.Project();
project.setName("MyNewProject");
// Do something useful...
project.delete(); // Release Emscripten object
});
objects created must be deleted
memory size is finite
write tests
class MyClass {
bool makeAllTheThings();
}
//Binding:
interface MyClass {
void makeAllTheThings(); //Oops
};
expect(obj.makeAllTheThings()).to.equal(true);
generated files are
huge
-
not a problem on the server
-
for clients, compress the file:
app.use((req, res, next) => {
if (req.originalUrl === "/libGD.js") {
req.url = "/libGD.js.gz"; // Silently switch to the .gz file
res.setHeader('Content-Encoding', 'gzip');
res.setHeader('Content-Type', 'application/javascript');
}
next();
});
advantages
-
can be used on entire, real-world codebases
-
raw performance on browser optimizing asm.js can be up to ~0.5x native code speed
-
no plugin, no intrusive change, real compiler
for which usage
https://github.com/4ian/GDevelop.js
need an example of a real codebase?
-
re-use a complex algorithm
-
re-use business logic of an app
-
game engines
Unity, emulators...
-
existing libraries
Physics engines, image processing...
thank you!
emscripten
By Florian Rival
emscripten
- 2,672