Emscripten has a handy tool called "Embind" for binding JavaScript/TypeScript and C/C++/whatever code. It's underappreciated and not well documented all in one place, but here is a soup-to-nuts summary.
Emscripten + Embind allow you to subclass and implement C++ interfaces in TypeScript, and easily call back and forth, even pass typed function pointers back and forth, using them to call C++ from TypeScript and TypeScript from C++!
I'm using it for the WASM version of Micropolis (open source SimCity). The idea is to be able to cleanly separate the C++ simulator from the JS/HTML/WebGL user interface, and also make plugin zones and robots (like the monster or tornado or train) by subclassing C++ interface and classes in type safe TypeScript!
emscripten.cpp binds the C++ classes and interfaces and structs to JavaScript using the magic plumbing in "#include <emscripten/bind.h>".
There is an art to coming up with an elegant interface at the right level of granularity that passes parameters efficiently (using zero-copy shared memory when possible, i.e. C++ SimCity Tiles <=> JS WebGL Buffers for the shader that draws the tiles) -- see the comments in the file about that):
/**
* @file emscripten.cpp
* @brief Emscripten bindings for Micropolis game engine.
*
* This file contains Emscripten bindings that allow the Micropolis
* (open-source version of SimCity) game engine to be used in a web
* environment. It utilizes Emscripten's Embind feature to expose C++
* classes, functions, enums, and data structures to JavaScript,
* enabling the Micropolis game engine to be controlled and interacted
* with through a web interface. This includes key functionalities
* such as simulation control, game state management, map
* manipulation, and event handling. The binding includes only
* essential elements for gameplay, omitting low-level rendering and
* platform-specific code.
*/
[...]
////////////////////////////////////////////////////////////////////////
// This file uses emscripten's embind to bind C++ classes,
// C structures, functions, enums, and contents into JavaScript,
// so you can even subclass C++ classes in JavaScript,
// for implementing plugins and user interfaces.
//
// Wrapping the entire Micropolis class from the Micropolis (open-source
// version of SimCity) code into Emscripten for JavaScript access is a
// large and complex task, mainly due to the size and complexity of the
// class. The class encompasses almost every aspect of the simulation,
// including map generation, simulation logic, user interface
// interactions, and more.
[...]
class_<Callback>("Callback")
.function("autoGoto", &Callback::autoGoto, allow_raw_pointers())
[...]
Here's the WebGL tile renderer that draws the tiles directly out of a Uint16Array pointing into WASM memory:
/**
* @file callback.h
* @brief Interface for callbacks in the Micropolis game engine.
*
* This file defines the Callback class, which serves as an interface
* for various callbacks used in the Micropolis game engine. These
* callbacks cover a wide range of functionalities including UI
* updates, game state changes, sound effects, simulation events, and
* more. The methods in this class are virtual and intended to be
* implemented by the game's frontend to interact with the user
* interface and handle game events.
*/
class Callback {
public:
virtual ~Callback() {}
virtual void autoGoto(Micropolis *micropolis, emscripten::val callbackVal, int x, int y, std::string message) = 0;
[...]
callback.cpp implements just the concrete ConsoleCallback interface in C++ with "EM_ASM_" glue to call out to JavaScript to simply log the parameters of each call:
/**
* @file callback.cpp
* @brief Implementation of the Callback interface for Micropolis game
* engine.
*
* This file provides the implementation of the Callback class defined
* in callback.h. It includes a series of methods that are called by
* the Micropolis game engine to interact with the user interface.
* These methods include functionalities like logging actions,
* updating game states, and responding to user actions. The use of
* EM_ASM macros indicates direct interaction with JavaScript, typical
* in a web environment using Emscripten.
*/
js_callback.h contains an implementation of the Callback interface that caches a "emscripten::val jsCallback" (an enscripten value reference to a JS object that implements the interface), and uses jsCallback.call to make calls to JavaScript:
Then you can import that TypeScript interface (using a weird "<reference path=" thing I don't quite understand but is necessary), and implement it in nice clean safe TypeScript:
Emscripten + Embind allow you to subclass and implement C++ interfaces in TypeScript, and easily call back and forth, even pass typed function pointers back and forth, using them to call C++ from TypeScript and TypeScript from C++!
Embind: https://emscripten.org/docs/porting/connecting_cpp_and_javas...
Interacting with Code: https://emscripten.org/docs/porting/connecting_cpp_and_javas...
Embind's bind.cpp plumbing: https://github.com/emscripten-core/emscripten/blob/main/syst...
C Emscripten macros (like EM_ASM_): https://livebook.manning.com/book/webassembly-in-action/c-em...
Emscripten’s embind: https://web.dev/articles/embind
I'm using it for the WASM version of Micropolis (open source SimCity). The idea is to be able to cleanly separate the C++ simulator from the JS/HTML/WebGL user interface, and also make plugin zones and robots (like the monster or tornado or train) by subclassing C++ interface and classes in type safe TypeScript!
emscripten.cpp binds the C++ classes and interfaces and structs to JavaScript using the magic plumbing in "#include <emscripten/bind.h>".
There is an art to coming up with an elegant interface at the right level of granularity that passes parameters efficiently (using zero-copy shared memory when possible, i.e. C++ SimCity Tiles <=> JS WebGL Buffers for the shader that draws the tiles) -- see the comments in the file about that):
emscripten.cpp: https://github.com/SimHacker/MicropolisCore/blob/main/Microp...
Here's the WebGL tile renderer that draws the tiles directly out of a Uint16Array pointing into WASM memory:https://github.com/SimHacker/MicropolisCore/blob/main/microp...
The corresponding C++ source and header and TypeScript files define the callback interface and plumbing:
callback.h defines the abstract Callback interface, as well as a ConsoleCallback interface that just logs to the JS console, for debugging:
callback.h: https://github.com/SimHacker/MicropolisCore/blob/main/Microp...
callback.cpp implements just the concrete ConsoleCallback interface in C++ with "EM_ASM_" glue to call out to JavaScript to simply log the parameters of each call:callback.cpp: https://github.com/SimHacker/MicropolisCore/blob/main/Microp...
js_callback.h contains an implementation of the Callback interface that caches a "emscripten::val jsCallback" (an enscripten value reference to a JS object that implements the interface), and uses jsCallback.call to make calls to JavaScript:js_callback.h: https://github.com/SimHacker/MicropolisCore/blob/main/Microp...
Then emscripten/embind generates a TypeScript file that defines the JS side of things:micropolisengine.d.ts: https://github.com/SimHacker/MicropolisCore/blob/main/microp...
Then you can import that TypeScript interface (using a weird "<reference path=" thing I don't quite understand but is necessary), and implement it in nice clean safe TypeScript:https://github.com/SimHacker/MicropolisCore/blob/main/microp...
It's all nice and type safe, and Doxygen will even generate documentation for you:https://micropolisweb.com/doc/classJSCallback.html
And it even works, and it's pretty fast! (Type "9" to go super fast, but for the love of god DO NOT PRESS THE SPACE BAR!!!)
https://micropolisweb.com