All rights reserved © Pierstoval
Moon art from Lawlie.
(Support artists by paying them)
Background from Solar System Scope

vendredi 30 décembre 2016

[Learning Sails] #3 - La fin des haricots

Voilà ! Maintenant ça fonctionne.

Mais vu que je voulais faire un jeu, se posait le problème du javascript côté client.

Et Sails intègre Grunt, donc on en profite (et en plus c'est pas très long)



Des assets


J'ai toujours été un utilisateur de Gulp, donc passer à Grunt n'est pas si difficile que ça.

En fait, là où c'était compliqué, c'était dans la façon que Sails a de gérer les assets.

En fait, quand on développe en NodeJS, il faut savoir que c'est le script de lancement qui définit quel est le dossier "public" qui sera accessible au serveur web.

Du coup, quand on fait sails lift, la merveilleuseté de Javascript asynchrone exécute Grunt en arrière-plan, en plus d'exécuter le serveur web de NodeJS.

Du coup, dès qu'on modifie un fichier de type "asset" (javascript client, css, less, sass...), Grunt va automatiquement recompiler tous les assets, et on peut recharger la page tranquillement pour voir les modifs.

Sauf que.

Trop de magie tue la magie


Quand on regarde la doc de Sails sur les assets (qui est un peu trop indigeste et un peu trop "floue" à mon goût...), on finit par comprendre que Sails est capable de rajouter automatiquement tous les fichiers js/css dans notre layout.

En gros, ça veut dire qu'il saura ajouter les balises <link> et <script> qu'il faut aux bons endroits de notre layout.

Sauf que moi je veux développer un mini-jeu web, donc y'a plein de choses à prendre en compte, et j'aime les bonnes pratiques, le découplage et le code propre, donc je me suis dit que j'allais faire ça en ECMAScript 6 parce qu'on peut faire des classes et des trucs cools comme des require('un_module_js').

Maintenant, j'ai mis un moment à comprendre que j'étais obligé de créer un dossier à la main où stocker mes assets en ES6, je l'ai appelé "js_src" parce que ça me paraissait correct.

Sauf que pour le compiler, il faut passer par Grunt.

Et Grunt, il est exécuté au lancement du serveur.

Et les fichiers compilés sont placés dans le dossier .tmp en dev, et www en prod (évidemment, pourquoi autrement ?).

Donc j'ai du me passer de cette magie pour faire du banal ES6 côté client.

La magie de Sails a donc disparu.

Des modèles en front ET en back (toujours des problèmes de magie)


Maintenant il faut reprendre le problème des concepts et des paradigmes.

J'ai un serveur de jeu d'un côté. De l'autre, j'ai pleiiiiin de joueurs (ouais, au moins 1 par mois).

Communication front et back (et oui, d'abord on parle du fonctionnement, ensuite des problèmes)


Pour communiquer, je le disais dans les autres articles, j'utilise des Web Sockets.

Côté serveur

  1. Le serveur de jeu stocke la liste des joueurs connectés. Pour chacun, il a son "plateau de jeu" enregistré en mémoire.
  2. Côté serveur, une fonction est exécutée toutes les 20 millisecondes pour recalculer les coordonnées de chaque joueur à chaque itération (à chaque "tick")
  3. Lorsque le recalcul est fait, on envoie son plateau de jeu à chaque joueur par websocket

Côté client

  1. Quand l'utilisateur accède à la page du jeu, une requête par websocket est envoyée pour qu'il puisse "s'inscrire"
  2. Quand l'inscription est "ok" (en gros, quand le serveur a répondu "ok t'es inscrit•e), il active le Drawer (dessinateur) qui va lui aussi exécuter une action toutes les 20 millisecondes (comme ça on a maximum 20 millisecondes de latence (sans compter la latence serveur), ce qui est pas mal), et active les différents évènements à envoyer/recevoir
  3. Le but du Drawer c'est de redessiner le plateau de jeu s'il a été modifié. Pour ça, il fait un simple différentiel des données de jeu renvoyées par le serveur.
  4. Les évènements à envoyer sont (eux aussi via websockets) les clics/appuis de touches, selon les cas
  5. Le navigateur va recevoir, via des websockets, chaque envoi du "tick" pour mettre à jour les informations du plateau de jeu et les garder en mémoire

Tout ceci peut être magnifiquement représenté sur ce schéma (oui, j'aime les diagrammes) :



Du coup, j'ai du faire un choix (et j'ai même posé la question sur Twitter pour savoir si c'est une bonne pratique), savoir si je partageais certains scripts en front ET en back.

J'ai fait ce choix pour le Serializer, mais pas pour les modèles globaux. En gros, l'objet chargé de (dé)sérialiser les données qui arrivent par websocket est commun au front et au back, et je pense le découper en deux : un Serializer d'un côté, et un Deserializer de l'autre (j'essaye de garder les même mots qu'utilise Symfony pour son composant Serializer, mais c'est pas facile, la terminologie en programmation...). Mais ce sera vraiment pour plus tard.

En attendant, la présence de ce Serializer me permet d'avoir quasiment les mêmes objets en front et en back, donc c'est très facile de s'y retrouver, et au niveau mémoire, le client n'est pas trop gourmand pour le coup.

Le plus gros des performances reste au niveau de la synchro et de la sérialisation des données.

En attendant, j'ai RÉUSSI à finir le jeu.

Et il est visible ici :

https://soundgame.herokuapp.com/game/game


Il m'aura donc fallu près de 3 mois pour développer un mini-jeu web, tout seul, from scratch, et en découvrant un framework jusque-là inconnu.

*Fierté*