Hallo Habr! In diesen nicht allzu fernen Jahren, im ersten Jahr der "Programmierer" -Fakultät, stellte ich meinen Kommilitonen gern die Frage: "Warum bist du überhaupt hierher gegangen, um zu studieren?" Natürlich habe ich keine genauen Statistiken über die Antworten geführt, aber ich erinnere mich mit Sicherheit: Mehr als die Hälfte wollte Spiele machen. Die meisten derjenigen, die auf diese Weise antworteten, waren nicht bereit für die Fülle verschiedener Arten von „Mathematikern“ und „Physikern“, mit denen wir in den ersten zwei Studienjahren überfordert waren. Nicht alle überlebten - am Ende des zweiten Jahres der fünf überfüllten Gruppen waren drei unvollständig.
Vor nicht allzu langer Zeit hatte unser Front-End-Team die Gelegenheit, sich in der Rolle des Gamedev zu versuchen. Ganz kurz lautet die Aufgabe: ein echtes 3D-Spiel zu erstellen, damit Sie einfach durch Öffnen des Browsers spielen können. Sogar mobil. Auch in der Webansicht.

, , , — React + Redux, « », , , .
RnD , : « CodeFest — . — ». , , , . Gods in the sky.

CodeFest .
MMO TPS , , . , , , . CodeFest. , .
2 , , 2 . , , .
: -, — , . , N , — 1 . , — 15 . , , 16-. , , :
- 30 . CodeFest, — , «». , .
- «» . , :
- 90 CPU;
- 120 ;
- 270 /c — , 675 /c — .
- 4,2 /c. , . — UDP WebRTC, . .
- . 2 , , , . , - Node.js , .
, , ( ?), , , , . .
, . , «». — .
:
, , , . , , 5 , , 40 .
-. , , — . React Redux. c 3D-, , Gods in the sky, three.js.
, .
:

, Node.js. :
- , - ( , IE11);
- access-;
- html- .
SSR . kubernetes , . , . , , , , ( ). . Nginx.
. TS, AppState, :
interface AppState {
gameState: GameState;
gameObjects: GameObjects;
data: Data;
requests: RequestsState;
}
requests
— , — . , — . .
data
— , , , , «». .
GameState
:
stage: 'factoids' | 'citySelect' | 'flyShow' | 'game' | 'results';
— . :
elapsedTime: number;
— , , .
, factoids
flyShow
, , — elapsedTime
. 0, . , . , , , . , . … - , .
, , :
interface GameObjects {
user: UserInGame;
gifts: { [id: string]: GiftInGame; };
boosts: { [id: string]: Boost; };
bombs: { [id: string]: Bomb; };
}
, : , , . — .
GameObjects — . , FPS . GPU 100 . Redmi Note 7 40 FPS ( ). MI 5S 30 FPS, 20 , .
, . .
- Redux, — . Redux , . ? Redux, .
, . , . «» ( . behavior — ). , .
. — tick, . , . tick . :
- , , , . , , , .
- ( Redux-), .
, «» . - , gameObjects, . , «», .
25 ( ).
. ( ), React, . - , «- » ( , , ).

. pixabay.com
, FPS- 13. , , - MI 5S, FPS- — 5-6, . .
: performance — , . , , .
FPS=30 , :
- Redux
FPS * ~[ ] ~= 750
, , . - React’
FPS * ~2 * ~[ ]
. - three.js , , WebGL . , .
- (.. 150 ) 4-6 , .. .
- CSS- .
- , FPS 2-4 ( , «» ).
- React.memo, - , .
React :
* ~2
, connect
. connect
— - HOC, , .- , React – (render pass), CPU, GPU.
: React.memo , , «», , DOM-, .
, React + Redux, .
Redux
Redux — . ~1 , . ~1, - — . -, , GameObjects , :
export function setNextGameObjects(payload: GameObjects) {
return {
type: 'SET_NEXT_GAME_OBJECTS' as const,
payload,
};
}
GameObjects :
- , .
SET_NEXT_GAME_OBJECTS
. - , , GameObjects. Redux , - .
SET_NEXT_GAME_OBJECTS
.
:
let state = store.getState().gameObjects;
for (const action of this.collectedActions) {
state = gameObjectsFakeStateReducer(state, action);
}
store.dispatch(setNextGameObjects(state));
collectedActions — , . — , .
— ~25 10 : ~2 . , gameObjectsFakeStateReducer . 25 . , .
? , perf-. 3 :
- ES2018, ~50 < ES2018 (, Firefox — ). , spread.
- Object.assign 300 .
- 500 , « » .
, , , — ES2018, . Firefox.
№3 , … . .
Object.assign. gameObjectsFakeStateReducer
gameObjectsFastStateReducer
, - :
switch (action.type) {
case 'PARTIAL_GIFT_STATE_PATCH':
if (!state.gifts[action.payload.id]) {
return state;
}
Object.assign(state.gifts[action.payload.id], action.payload);
break;
}
? , . , . 25 1 2-4 ( CPU). :

«» , , Redux 10 . , - , , connect gameObjects. , .
React
React . — DOM. , , — reselect, React.memo/React.PureComponent, shouldComponentUpdate . .
, , ~2, — 5. , React , .. DOM .
batch React + Redux. , — . , ?
, , connect . , .
. , <ScoreBoard />
,
- ;
- , .

React.PureComponent, connect mapStateToProps. elapsedTime
score
. Score
, elapsedTime
— . elapsedTime
- render. , FPS, shouldComponentUpdate. HOC’a connect — shouldComponentUpdate .
, «» , :
- shouldComponentUpdate,
elapsedTime
( ). , HOC’a connect. - mapStateToProps . , , . connect’a.
- useSelector. , .. . , , , .
- — connect, — . , . ScoreBoard .
— ! ScoreBoard’a connect, , . mapStateToProps , .
, connect. , — - - canvas-, three.js (, , .). render , :
return <div id="map" className={s.map} ref={mapRef} />;
return <canvas id="scene" className={s.scene} ref={sceneRef} />;
, DOM — . , React . , componentDidUpdate, . , componentDidUpdate :
public componentDidUpdate() {
const { geoPos, orientedRotationQuanternion } = this.props;
const { map } = this.state;
map.setQuat(orientedRotationQuanternion);
map.setCenter([geoPos[0], geoPos[1]], { animate: false });
map.setZoom(getMapZoomFromHeight(geoPos[2]), { animate: false });
}
, componentDidUpdate . :
- connect. , mapStateToProps;
public shouldComponentUpdate() { return false; }
— «» «»;- Redux ( ) useStore ;
- (
store.subscribe(() => { /* … */})
); - ;
- !
:
store.subscribe(() => {
const state = store.getState();
const { map } = this.state;
const { geoPos, orientedRotationQuanternion } = state.gameObjects.user;
map.setQuat(orientedRotationQuanternion);
map.setCenter([geoPos[0], geoPos[1]], { animate: false });
map.setZoom(getMapZoomFromHeight(geoPos[2]), { animate: false });
});
, , .
, - , .

. — React Tree Reconcilation + Commit

React Tree Reconcilation + Commit
React + Redux , :
- , , , «» React , ;
- ;
- « », , ;
- , .
, . , . , . ? .
:
- , , , , . , .
- , React’ 30 . : .
- , .
- . . , .
- .
- Performance — . CPU — , Intel i9.
- - : 12–20 – . , .
- , . , , . , .
- , , . IE11 , . 70- Edge 18- ( ).
- WebGL . - , , -, Firefox linux, . . — . -, . , , « », .
, :
- Firefox. . , WebGL, — spread. FPS Firefox , Chrome. , , .
- IPhone. Safari IE6 220 . , , . , , . . — - .
- . . . , , (, , ). .
- . , , . , , , «» .
- ( ), – , . .
, , . iOS, Android Web .