Ix Dev 6 - React
UI in React
I've long used React in my professional work and at this point find it natural to think in. Going back to imperative UI models, especially for games, was quite painful - having to keep track of state differences, or emit lots of events.
A few years back I started using React for my web-based games - both as a primary control (incrementals), and as an in-game toolbar/window/modal solution.
This works very neatly with a single giant state object - it's similar in concept to Redux. But performance needs put immutable structures far out of reach, as the allocations and garbage generated would far outweigh savings in a well-written UI.
Performance
The best bang-for-buck in React optimization is to reduce the number of times a component (and its children) renders. If there's a component rendering 60 times per second - you can make each render a few milliseconds faster, but you should probably just not render in the first place.
Modern Redux solves this through an immutable state object and useSelector(state => state.someSubset) selectors. (Simplified) useSelector listens to state update notifications from the Redux store (eg after dispatch) and uses reference equality to determine if the component should re-render; every update to state guarantees a new object at that level and above, so things tend to just work out. Care needs to be taken around arrays and deeply nested objects.
I use a similar set of useGameSelector objects, using reference equality as the default - it again tends to work out nicely with specific-enough selectors. The game engine notifies selectors every 100ms, or "significant" event. Selectors can be further optimized by setting either the cacheBy or deepCompare fields: cacheBy allows limiting selector recalcs to once-per-tick or once-per-cache-update (generally "per user interaction"), while deepCompare uses JSON.serialize to compare values.
In my first efforts to use React with games, I used a simpler useLiveGameState(), which always re-rendered every game tick (100ms) or on user interaction. I was able to keep it performing alright at first by carefully passing down values and props, but it proved unwieldy, and the constant renders began eating a visible portion of the update cycle.
The first switch to useGameSelector - even before cacheBy optimizations - was enough to remove UI updates from the flame graph when compared to the rendering system and game logic itself. After that point, I've tweaked and optimized, but generally only if I notice a component re-rendering too much - it just hasn't been worth the time.
Code
This is the full code for this iteration of useGameSelector - originally inspired by Redux.
Loading syntax highlighter...