Dans ce post je voudrais revenir sur certaines erreurs à ne pas faire lorsqu’on développe un jeux vidéo.

Et même si le c++ est mon langage préféré et selon moi c’est le seul langage vraiment viable pour développer des jeux vidéos, je vais essayer de ne pas faire de polémique.

Ceci n’est que un retour de mon expérience dans le domaine, pas une obligation (je ne vous oblige donc pas à utiliser le c++), si vous n’êtes pas d’accord avec ses principes libre à vous de faire autrement.

Erreur n°1 : utiliser plein de thread c’est plus performant.

Il faut savoir que l’utilisation de thread requiert souvent au besoin d’utiliser des primitives de synchronisation, et les locks ne sont pas gratuit de plus leur oublier peut amener à des crashs difficile à détecter, et peut engendrer pas mal de problèmes (inter-blocages, ralentissement de l’application, etc…)

En effet, faire des locks trop souvent cela représente un peu près 30% du temps d’exécution de votre application. (J’ai testé avec un profiler)

Il faut donc endormir les threads afin de ne pas faire de locks trop souvent, tout en gardant un rendu fluide, chose qui n’est pas aisée!

Certains langages moderne utilise des threads pour gérer les événements, update les composants, etc…, ce qui est sale et pas performant.

Par exemple, afficher 12345678 en console avec un seul thread ne prendra pas plus de temps que d’afficher 1234 et 5678 avec deux thread différents.

De plus chaque thread possède déjà des traitements qui se font en parallèle au niveau de l’os (j’entends par là les opérations monothread qui sont effectuée par le noyaux de linux par exemple (hyper-threading) et qui offre des optimisations de l’ordre de * 200), donc, si vous les séparer, dans plusieurs threads il faut faire pas mal de benchmark et ça rend les choses beaucoup plus compliquée sans pour autant être sûr de vraiment gagner en performance, le but est de rester simple donc et d’ajouter de la complexité seulement si cela est vraiment nécessaire. (KISS : Keep it simple and stupid)

Pour les traitements graphique il y a le GPU qui complète très bien le CPU.

De plus, les api graphiques évoluent de manière à pouvoir faire de plus en plus de traitements parallèle entre le CPU et le GPU de manière performante à l’aide de shaders, des FBO, etc…

Les threads ne sont donc à utiliser que là ou l’on en a vraiment besoin afin de pouvoir effectuer des traitements lourd sans “freeze” l’application comme par exemple une sauvegarde automatique des états de l’application, mais, généralement il n’y a pas eu d’autre cas jusqu’à présent dans lequel j’ai eu obligatoirement recours à des threads, je sais que j’en utilise pas mal dans ODFAEG des threads mais ceci est une erreur de ma part je pense donc que je vais proposer deux implémentations différente, une implémentation “threadée” et une autre “non-threadée”.

En résumé, avec des threads, le code devient beaucoup plus difficile à déboguer et à maintenir, au pire, si il y a des goulots d’étranglement, essayer d’optimiser les fonctions qui sont les plus souvent appelée.

Erreur n°2 : Ajouter trop de fonctionnalité à une classe dont ce n’est pas son rôle.

Il faudrait limiter les classes à un voir deux rôles maximum, prenons par exemple une classe fenêtre, celle-ci a 2 rôles, s’afficher et générer des événements, il ne faut donc pas qu’elle s’affiche en plus de traiter les événements utilisateurs.

L’idéal aurait été de séparer le code qui génère les événements du code de la fenêtre, car, il se pourrait très bien aussi que l’on veille gérer des événements lorsqu’il n’y a pas de fenêtre comme par exemple pour les applications console, malheureusement, SFML ne permet pas de faire cela sans devoir créer une fenêtre. (Même si ce n’est pas très gênant et qu’il suffit de créer une fenêtre invisible)

Par contre SDL, si je ne m’abuse, permet de le faire. 🙂

Il faut donc essayer de respecter au maximum le principe de classes à responsabilité unique.

Les transferts de données peuvent se faire via d’autres classes ou interfaces communes.

Erreur n°3 : Vouloir coder toutes les fonctionnalités d’une application d’un seul coup.

Il faut faire pas mal d’itération et de tests intermédiaires, ceci afin d’éviter d’avoir trop de bugs à corriger à la fin de l’application, vous aller me dire, comment faire cela si on a besoin de tel modules et tel modules pour tester cela ? Le but est de rendre les modules les plus indépendants possible, et de coder de manière générique, il ne faut pas que votre code ressemble à une usine à gaz avec 36 000 classes qui communiquent à travers vos différent modules, en principe, juste une classe suffit (en général c’est la classe application), certains bon langages de programmations permettent de coder de manière générique de plus en plus simplement tout en respectant les principes de l’orienté objet et permettant en plus de faire de la méta-programmation de manière aisée sans devoir modifier les fichiers sources avec des scripts en compilation. (CF les templates variadiques du c++11 et du c++14)

Pour les différentes étapes des itérations je vous conseille de vous renseigner du côté de l’extrême programming ainsi que d’appliquer le principe de YAGNI. (You ain’t gonna need it)

Erreur n°4 : Utiliser un langage qui utilise un garbage collector c’est mieux.

Hum non. x)

Pour libérer la mémoire automatiquement il faut tester si elle n’est plus référencée et ceci n’est pas toujours gratuit même si certains langage moderne tentent de le faire de manière intelligente. (Avec parcours d’une partie de la mémoire pendant des temps de pause)

Ceci reste coûteux si il y a beaucoup de ressources à libérer et de plus on ne sait pas quand exactement celles-ci vont être libérée. (A éviter donc dans un jeux vidéo qui utilise beaucoup de ressources)

De plus, pour des raisons de logique plutôt, le programmeur devrait toujours savoir quelle classe est propriétaire de la ressource mémoire. (RAII)

Le programmeur devrait aussi savoir si la ressource peut être copiée ou pas, si elle est référencée ou bien copiée, les nouveaux langages cache cette sémantique ce qui complique les choses lors de la relecture du code source.

Certains bons langages de programmation possède des classes qui servent de capsule RAII, ce qui est le cas par exemple de la classe std::unique_ptr en c++14 (mais ce n’est pas la seule), et c’est la classe qui devrait être utilisée dans la majorité des cas de plus, avec la move semantic ont peut changer une ressource de propriétaire de manière performante, et pour utiliser une ressource de manière temporaire il suffit de récupérer un pointeur nu sur la ressource.

Par contre std::shared_ptr et std::weak_ptr ne sont à utiliser que là ou c’est nécessaire, jusqu’à maintenant je n’en ai pas eu besoin.

On peut s’en passer facilement, pour s’en passer il suffit pour cela de créer un thread en début d’application, de faire un pool dessus, et à la fin de l’application, attendre que le thread ai fini sont exécution avant de détruire les ressources, cela ne coûte rien et vous permet d’endormir de temps à autre le thread courant ce qui réduit la consommation car quand un thread est mit en attente il ne fait rien!

Donc vous pouvez réduire le tour de boucle d’un thread sans que cela affecte le tour de boucle d’un autre thread qui affiche le jeux par exemple et c’est vraiment là que les threads sont utile. (Et là encore on pourrait encore se passer de threads avec un timer et un test.)

Il faut savoir que le c++14 est un nouveau langage qui se veut performant, sécurisé et économique, alors, autant utiliser toutes ses fonctionnalités à bien.

Erreur n° 5 : Vouloir à tout pris recompiler car cela est plus rapide à l’exécution.

Hum, oui un peu.

Mais la différence est négligeable, par rapport au temps que ça demande de devoir recompiler une librairie à chaque fois que l’ont change de module!

La compilation d’une grosse application peut nécessité plusieurs minutes voir même plusieurs heures pour un temps d’exécution qui sera réduit de quelque micro-secondes à peine.

Utiliser trop de dll n’est pas bon certes, donc, il faudra bien sûr vous limiter lors du chargement de modules pour pas que l’application mette trop longtemps à charger, mais, si par exemple vous souhaiter utiliser le module  window de sfml et puis que vous voulez changer et utiliser le module window de SDL parce que SFML n’est plus maintenue, cela sera un grand gains de temps surtout si l’on veut tester divers modules et que l’on doit changer de module souvent.

Voilà 5 grosses erreurs qui selon moi sont à éviter lorsque l’on développe des jeux vidéos qui sont très coûteux en matière de performances.