React and WebAssembly to Rewrite Native Apps
Native Web Apps
@FlorianRival
open-source, cross-platform game creator designed to be used by everyone
- An editor for games
- Visual programming
- Output HTML5 games
The editor is getting old
- Lots of cross-platforms issues with the UI toolkit, support for macOS and Linux is bad.
- Iterating on the software is slow (C++ 😬)
- Building UI is slow...
- ...and limited to old UI components...
- ...and UX would need some enhancements 😇
- The entry barrier for new contributors is high (C++ 😬)
It's time to react!
Could web technologies help?
React is perfect for making complex UI
Well, really?...
Can we have
list and trees of hundreds of elements,
dynamic panels,
2d/3d visualizations
nested dialogs,
color pickers, tabs, file trees, a "real app" feeling
drag'n'drop, search,
, copy-paste,
edit code in an editor...
Can we make ambitious apps?
2d/3d visualizations
, nested
JavaScript is perfect for most applications
Well, really?...
what if we
already have a native codebase?
Or have advanced
computations,
or need consistent perfs?
GUI (windows, dialogs...)
Filesystem
React.js powered interface
Node.js/browser adapters
X-platform toolkit (wxWidgets)
GDevelop C++ codebase
GDevelop C++ codebase
Core classes (game, scene, object, textures...)
Game exporter (JS "transpiler")
libGDevelop.js
JS
Writing WebAssembly
Using Emscripten
git clone https://github.com/emscripten-core/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...)
Expose existing classes
class Layout : public ObjectsContainer {
public:
void setName(const gd::String & name_) { name = name_; };
const gd::String & getName() { return name; };
Object& insertObject(const Object& object, size_t position) { ... };
Object& getObject(const gd::String& name) { ... };
}
Expose existing classes (2)
interface Layout {
void setName([Const] DOMString name);
[Const, Ref] DOMString getName();
[Ref] Object insertObject([Ref] gdObject object, unsigned long pos);
[Ref] Object getObject([Const] DOMString name);
}
"WebIDL" bindings for Emscripten WebIDL binder:
Use Emscripten classes
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
});
- Converted, including JS strings (to char*)
Primitive types?
If I pass an object?
- std::iostream is binded to console.log
- Objects references are converted to pointers (or references) by the webIDL generated glue code
Can I debug?
Things to know
const layout = new gd.Layout();
// ...
gd.destroy(layout); // Binded objects need manual destruction
- Memory management requires care!
> console.log(layout);
{ ptr: 6804120 }
Wasm memory
The content of gd.Layout
- For React:
componentDidMount() { this._project = new gd.Project(); }
componentWillUnmount() { gd.destroy(this._project); }
or use an Effect Hook 🔥
Things to know (2)
Output files can be large (~3mb for GDevelop),
but gzip helps (down to ~1mb).
Things to know (3)
- A complete test set is invaluable for ensuring that no errors are in the bindings.
abort(16). Build with -s ASSERTIONS=1 for more info.
- Wrong type for a parameter
- Parameter forgotten
- Using a deleted object
What can go wrong?
GUI (windows, dialogs...)
Filesystem
React.js powered interface
Node.js/browser adapters
GDevelop JS codebase
GDevelop C++ codebase
Core classes (game, scene, object, textures...)
Game exporter (JS "transpiler")
libGDevelop.js
Creating a complex UI?
Find a component library
- Extensive list of high quality components
- Good theming support
- Accessibility
- Documentation quality
Large lists (with drag'n'drop)
Virtualized lists
const ObjectsList = (props) => (
<List
rowRenderer={({ ... }) => {
<ObjectRow ... />
})
/>
);
const ObjectRow = () => <div>...</div>;
const ObjectsList = (props) =>
props.map(group => <ObjectRow ... />)
react-dnd
react-virtualized
react-window
Panels/resizable editors
react-mosaic
Use a tiling window library
Large trees (with drag'n'drop)
Virtualization again!
react-sortable-tree
Levels rendering or visualizations
Forget the DOM
Don't forget the DOM
<svg
onPointerMove={...}
onPointerUp={...}
>
<polygon
stroke="rgba(255,0,0,0.5)"
points={vertices.map(vertex =>
`${vertex.getX()},${vertex.getY()}`
).join(' ')}
/>
{vertices.map((vertex, j) => (
<circle
onPointerDown={...}
key={`vertex-${j}`}
fill="rgba(255,0,0,0.75)"
cx={vertex.getX()}
cy={vertex.getY()}
r={5}
/>
))}
</svg>
When things aren't fast enough
Profile the rendering
Measure performance in production (React development build is way slower)
Inspect calls to WebAssembly
- Beware of overhead in binding code/wasm call when calling functions, in particular if rendering large lists
objects // list.length === N
.filter(object => {
return object.getType() !== "";
}) // N wasm calls + string conversions
.map(object => {
return object.getVariables(project)
}); // N wasm calls + object conversions
- It might be useful to store strings/values on the JS side if you know they won't change.
When your codebase is growing
Components for everything
<FlatButton style={{fontSize: 12}}>
Help
</FlatButton>
<FlatButton>
Help
</FlatButton>
😒
🙂
🤩
<HelpButton />
Avoid custom/adhoc styles, that won't scale:
Make a component for it! #DesignSystem
Don't unit test your visual components...
Well actually, do, but only for part where there is logic
...but create visual stories
Storybook
React Styleguidist
Use types
- Will tell you that you (or someone else) forgot to pass/remove a prop
Flow
Typescript
- Help auto-completion
- Avoid most silly (or not so silly) mistakes
- Help to "document" your objects
Types shine when you or someone else is refactoring some parts of the app
GUI (windows, dialogs...)
Filesystem
React.js powered interface
Node.js/browser adapters
GDevelop JS codebase
GDevelop C++ codebase
Core classes (game, scene, object, textures...)
Game exporter (JS "transpiler")
libGDevelop.js
Consider your packaging options
Progressive Web App
Electron (or nw.js)
Embed in a native app
Build over Electron/native APIs
GUI (windows, dialogs...)
Filesystem
React.js powered interface
Node.js/browser adapters
GDevelop JS codebase
GDevelop C++ codebase
Core classes (game, scene, object, textures...)
Game exporter (JS "transpiler")
libGDevelop.js
Result?
What are users saying?
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
It works!
The native web app is better than the native app
Ultra fast iterations,
ultra fast testing,
near perfect cross-platform,
faster startup time,
auto updates,
more contributors,
"try it online",
tablets/phones port
Thanks! 🙂
@FlorianRival
gdevelop.io
github.com/4ian/GDevelop
Native Web Apps, version 2
By Florian Rival
Native Web Apps, version 2
React, useful modules from the ecosystem and WebAssembly to create ambitious apps. Patterns and tips for create scalable applications at a fast pace.
- 2,683