React, Javascript and WebAssembly to Port Legacy Native Apps
@FlorianRival
gdevelop-app.com
Β open-source, cross-platform game creator designed to be used by everyone
GDevelop C++ codebase
New GDevelop editor
GUI (windows, dialogs...)
Filesystem
Core classes (game, scene, object, textures...)
Game exporter
libGDevelop.js
React.js powered interface
Node.js/browser adapters
X-platform toolkit (wxWidgets)
LLVM IR (bitcode)
git clone https://github.com/juj/emsdk.git && cd emsdk
./emsdk install latest && ./emsdk activate latest
./emcc tests/hello_world.c
node a.out.js
For large projects, Emscripten provides replacements that swap GCC or Clang by Emscripten in build system (automake, CMake...)
class ObjectsContainer {
Object& InsertObject(const Object& object, std::size_t position) { ... };
Object& GetObject(const String& name) { ... };
}
class Layout : public ObjectsContainer {
public:
void setName(const gd::String & name_) { name = name_; };
const gd::String & getName() { return name; };
// ...
}
interface Layout {
void setName([Const] DOMString name);
[Const, Ref] DOMString getName();
// Inherited from ObjectsContainer
[Ref] Object insertObject([Ref] gdObject object, unsigned long pos);
[Ref] Object getObject([Const] DOMString name);
}
"WebIDL" bindings for Emscripten WebIDL binder:
Module().then(gd => {
const layout = new gd.Layout();
layout.setName("My game level");
const object = new gd.Object();
object.setName("My character");
layout.insertObject(object, 0);
console.log(layout.getName()); // "My game level"
console.log(object.getObject("My character")); // Returns a gd.Object
});
const layout = new gd.Layout();
// ...
gd.destroy(layout); // Binded objects need manual destruction
componentDidMount() { this._project = new gd.Project(); }
componentWillUnmount() { gd.destroy(this._project); }
or use an Effect Hook π₯
abort(16). Build with -s ASSERTIONS=1 for more info.
const SortableObjectRow = SortableElement(props => (
<ObjectRow ... />
));
const ObjectsList = (props) => (
<List
rowRenderer={({ index, key, style }) => {
<SortableObjectRow />
})
/>
);
const SortableObjectsList = SortableContainer(ObjectsList);
<SortableObjectsList
onSortEnd={({ oldIndex, newIndex }) => ...}
...
/>
+ react-sortable-hoc
const ObjectRow = () => <div>...</div>;
const ObjectsList = (props) => (
props.map(group => <ObjectRow ... />)
)
react-mosaic
componentDidMount() {
if (this.props.focusOnMount) {
this._searchBar.focus();
}
}
or use a Hook π₯
https://blueprintjs.com/docs/#core/components/hotkeys
<Hotkeys>
<Hotkey
global={true}
combo="shift + a"
label="Be awesome all the time"
onKeyDown={() => console.log("Awesome!")}
/>
</Hotkeys>
react-sortable-tree
<svg
onMouseMove={...}
onMouseUp={...}
width={this.props.imageWidth}
height={this.props.imageHeight}
>
<polygon
fill="rgba(255,0,0,0.2)"
stroke="rgba(255,0,0,0.5)"
fileRule="evenodd"
points={vertices.map(vertex =>
`${vertex.getX()},${vertex.getY()}`
).join(' ')}
/>
{vertices.map((vertex, j) => (
<circle
onMouseDown={...}
key={`vertex-${j}`}
fill="rgba(255,0,0,0.75)"
cx={vertex.getX()}
cy={vertex.getY()}
r={5}
/>
))}
</svg>
export default class ElectronMenuImplementation {
buildFromTemplate(template) {
this.menuTemplate = template;
return undefined;
}
showMenu(dimensions) {
if (!electron) return;
const { Menu } = electron.remote;
const browserWindow = electron.remote.getCurrentWindow();
this.menu = Menu.buildFromTemplate(this.menuTemplate);
this.menu.popup({
window: browserWindow,
x: Math.round(dimensions.left),
y: Math.round(dimensions.top + dimensions.height),
async: true, // Ensure the UI is not blocked on macOS.
});
}
}
class MaterialUIContextMenu extends React.Component { ... }
class ElectronContextMenu extends React.Component { ... }
export default class MaterialUIMenuImplementation {
buildFromTemplate(template) {
return template.map((item, id) => {
if (item.type === 'separator') {
return <Divider key={'separator' + id} />;
} else if (item.type === 'checkbox') {
return (
<MenuItem
...
/>
} else {
return (
<MenuItem
...
/>
}
}
showMenu() { ... }
}
Don't forget to measure performance in production (React development build is way slower)
objects.filter(object => object.getType() !== "") // n wasm calls + string conversion
.map(object => object.getVariables(project)); // n wasm calls + string conversion
Won't be noticeable in most UI situations, but can be if used in renderings or long lists/trees
Feedback loop is a few seconds while developing
vs
...and code signing, and upload, and creating installers for Windows, .DMG for macOS, .tar.gz/AppImage for Linux.
Since day one, GDevelop 5 is auto-updated.
Time between releases moved from months to weeks or days.
WebAssembly is on average 33.7% faster than asm.js, with validation taking only 3% of the time it does for asm.js
- Bringing the web up to speed with WebAssemblyΒ Haas et al., PLDI 2017
Wow, what a difference a year can makeΒ
but I have to admit, once you worked a bit with it, it could be really more productive on many aspects than GD4
Amazing how much easier and streamlined the engine has become