From a native app to a full JS app using Emscripten

About me

Florian Rival

Architect-developer @

Developer of

and

@florianrival

github.com/4ian

what we want to do

Desktop, C++ powered app

Full JS app running

in browsers

WTF?

Native + JS app running with Electron

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

App (Electron + React.js)

exposing  existing code

class Project : public ClassWithObjects {
public:
    void setName(const std::string & name_) { name = name_; };
    const std::string & getName() { return name; };

    // ...
}
interface Project {
    void setName([Const] DOMString name);
    [Const, Ref] DOMString getName();
}
  • take unmodified, existing code 

  • write bindings in "web idl"

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...

    gd.destroy(project); // 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!

[WebAssembly Paris] From a Native App to a Full JS app using Emscripten

By Florian Rival

[WebAssembly Paris] From a Native App to a Full JS app using Emscripten

  • 2,054