Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

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++!

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

  /** 
   * @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:

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

  /** 
   * @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:

callback.cpp: https://github.com/SimHacker/MicropolisCore/blob/main/Microp...

  /** 
   * @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:

js_callback.h: https://github.com/SimHacker/MicropolisCore/blob/main/Microp...

  #include <emscripten/val.h>
  #include "callback.h"

  class JSCallback : public Callback {
  public:
      explicit JSCallback(emscripten::val jsCallback)
          : Callback(), jsCallback(jsCallback) {}

      // Implement all pure virtual functions from Callback
      void autoGoto(Micropolis *micropolis, emscripten::val callbackVal, int x, int y, std::string message) override {
          jsCallback.call<void>("autoGoto", emscripten::val(micropolis), callbackVal, x, y, message);
      }

      [...]

  private:
      emscripten::val jsCallback;
  };
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...

  // TypeScript bindings for emscripten-generated code.  Automatically generated at compile time.
  [...]
  export interface Callback {
    autoGoto(_0: Micropolis, _1: any, _2: number, _3: number, _4: EmbindString): void;
  [...]
  export type MainModule = WasmModule & typeof RuntimeExports & EmbindModule;
  export default function MainModuleFactory (options?: unknown): Promise<MainModule>;
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...

  /// <reference path="../types/micropolisengine.d.ts" />

  import type { Micropolis, JSCallback } from '../types/micropolisengine.d.js';

  // Micropolis Callback Interface Implementation

  export class MicropolisCallbackLog implements JSCallback {

      verbose: boolean = false;

      autoGoto(micropolis: Micropolis, callbackVal: any, x: number, y: number, message: string): void {
          console.log('MicropolisCallbackLog: autoGoto:', 'x:', x, 'y:', y, 'message:', message);
      }
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



Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: