Wednesday, December 30, 2015

Je peux améliorer mon C++

Grande différence entre mon "nouveau" boulot (depuis Mars 2014) et mon ancien poste universitaire: ici, il y a des revues de code. Et mes collègues "Hergé et Jigé" ont un sacrément haut niveau en C++ comparé au mien. Alors autant profiter de mes deux semaines de "Super Papa Bros" pour essayer de remanier le code de mon moteur de jeu, le rendre plus fiable, plus lisible, et peut-être plus efficace.

J'avais introduit un mécanisme de gestion de mémoire inspiré du cours "compilateurs" : le "tank", avec un seul bloc de mémoire qui est découpé progressivement en sous-blocs qui auront tous la même durée de vie. L'ennui principal, c'est que ce "tank" n'a aucun moyen de retenir quels objets ont été créés ni d'appeler les destructeurs en fin de cycle. Du coup, tout objet "standard" présent dans les morceaux du tank sont une fuite de mémoire potentielle.


Parmi les "nouveaux trucs" appris cette année qui pourront m'être utiles, il y a la fonction "foreach", les fonctions template (et en particulier leur utilisation pour faire de la programmation assertive), les namespaces anonymes, et les structures-internes-pour-masquer-l'implémentation.


Let me collate a few C++ tricks I practiced this year and hope to use in my hobby tools/game engine to improve them.
If it make sense to have a function applied on all members of a collection, foreach can help:

- for (vector<Tire>::iterator it = wheels.begin(), e = wheels.end(); it != e; it++) {
- checkPressure(*it);
- }
+ for_each(wheels.begin(), wheels.end(), checkPressure);


Template function do exist. Template functions do not need their template argument to be specified when it can be inferred from function arguments. E.g.

template<typename T>
void assert(T a, T b, const std::string msg) {
  if (a != b) throw AssertException(msg);
}

can be invoked as
+ assert(myCar, TimeTravellingDelorean, "timed' out");
-assert<Car>(myCar, TimeTravelling ...);;

Template integers exist too. If you want something to behave completely differently depending on whether you're on a 32-bit or 64-bit system, you might consider the following function that can be invoked as getLibraryPath<sizeof(int)>():


template<int> path getLibraryPath();
template<> inline path getLibraryPath<4>() {
  return "/usr/lib32";
}
template<> path getLibraryPath<8>() {
  return "/usr/lib/x86_64-linux-gnu";
}

Note that only template declaration can fit within the class body. Specializations introduced with template<> must be out of the class block and have additional MyClass:: token.

And as we're talking about templates stack overflow's question on puzzling template error messages can help.

You don't need to declare your functions static to avoid interference with other translation units of the program. Simply put them in an anonymous namespace.

You don't need to explicitly track the "object setup sequence" with an init_level if you can do it with contents of the regular members of the objects,


Car::~Car() {
- switch(init_level) {
- case TIRES_MOUNTED: RecycleTires();
- case ENGINE_INSTALLED: RecycleEngine();

- // FIXME: what do you do for default: ?
- }

+ if (tires!=UNDEF) RecycleTires();
+ if (engine!=UNDEF) RecycleEngine();

PS: UNDEF could just be 0 for pointers to components.

You can have compact structure initialization with (optionally-)named fields but it must be *trivial*, e.g.

  • you may not swap the order of components;
  • you may not omit a field if there are other field after it
  • but you *can* omit items at the tail of the description

If you want a class/struct to look more like a first-class citizen, think about
  • copy constructor : Car(const Car &that) : engine(that.engine), tires(that.tires) {}
  • comparison operator : bool operator==(const Car &that) { return that.tires==tires && that.engine==engine; }
  • ostream-compatibility: this requires a additional std::ostream& operator<<(std::ostream& os, const Car& that) { os << "powered by " << engine << " on " << tires; return os; } function. Note that it is *not* a member of the Car struct/class and that it will need to be declared friend of the Class in case of a class.


Something thrown as throw new std::runtime_error(..) is caught by catch (...) { releaseResources(); throw; }, but not by catch(const exception& e). That latest one only catch stack-allocated exceptions, e.g. throw std::runtime_error(...);. Reading more on this I should.

I should remember that namespace ds = PPPTeam::LibGEDS is the way to say import PPPTeam.LibGEDS as ds. And that ostream & operator << (ostream &out, const Complex &c) is the way to tell how the class 'Complex' should be printed.

Oh, and I shouldn't use std::unique_ptr on stack-allocated object. ever. unique_ptr will eventually call free on the pointer it holds.

 

In case of doubt on performance, remember that Quick-Bench.com does exist.

Tuesday, December 22, 2015

Usage: 15% ; Leakage: 12%

I guess you all know what a "memory leak" is. Well, you might not have a lot of gameplay updates in the following weeks: I just enabled a sort of report that measures how much of the state created when parsing the title screen of School Rush. 12%. I've got some code-cleanup to go through.

Bon, bin les premiers résultats de l'analyseur mémoire intégré à mon système de test-de-code-DS-sur-x86 sont précis, mais aussi assez effrayant: 12% de la mémoire allouée lors de l'initialisation de l'écran-titre ne sont pas rendus à la fin du niveau. Dans un cas pareil, la mémoire de la DS (4Mo) finit systématiquement par être saturée après un certain temps d'utilisation du jeu, ce qui ce traduira fort probablement par un crash.

Allez, joyeuses fêtes à vous aussi. (Et, euh, non, n'espérez pas trop une version "spéciale Noël" de School Rush avec de la neige sur les bancs et des bonnets à ponpon sur les gommes sauteuses, du coup)

PS: I suspect this could be linked to the migration of most state-machine data structure into the "tank" memory allocator: because the tank doesn't know objects themselves, and that it's quite unlikely that it invokes their destructors when "flushing" the tank. I'll have to make sure that structures that were pushed into the tank are flat enough or that their sub-components are allocated in the tank too.

Monday, December 21, 2015

Rayman Designer

Quelque-part en 1997, Ubisoft sort "Rayman Gold", avec un éditeur de niveau. Curieusement, je passe mon tour. Pas trop envie d'acheter une 2eme fois le jeu que j'ai toujours pour pouvoir avoir l'éditeur de niveau ? Pas trop envie de me contenter de faire des maps dans un univers qui n'est pas le mien et d'en être réduit aux seuls ennemis déjà existant dans le jeu ? J'avoue que je ne saurais plus trop dire.

Years before Mario Maker was Rayman Designer. I never owned it, and it never quite appealed me to play custom maps, although I loved very much the original game. It's a bit hard to say why, but I think it could be reduced to two things.

First, the core gameplay is different, and the player is asked to collect every ting (read "golden coin" if it was a Mario game) to reveal the exit of the level. Okay, I love it when you have something more to do than reaching the exit, but being forced to grab every whatever to complete the level always felt annoying. I already found it lame near 2001 when I played Rayman II and discovered that without collecting all the tings of the games I couldn't access the last levels. But here you have to collect them all *level per level*. Oh, okay, you can select levels freely, but still. And the "colored tings that open gates" felt artificial, even more abstract than typical "keys and locks" and much less integrated than Commander Keen's coloured keys.


Pas de cages dans cet épisode, mais des "tings" de différentes couleurs, pouvant ouvrir des barrières quand on a récolté tous les tings d'une couleur donnée. Du coup, le level design ressemble assez fort à un mélange entre pacman et rayman. Un aspect qui m'a assez refroidi du côté de 2007 quand j'ai découvert tout ça sur wiki.

The second thing .. what would player's level look like. I did some game making myself between 1995 and 1997, and I know people who got granted the super-powers of creating levels tends to build impossible missions that they only can beat (sometimes cheating like being the only ones to know that lava is not quite lava in the 3rd pool and that the player needs to dive there to get access to the end of the level). Checking youtube videos of "Rayman by his fans" proved that this was mostly not the case, but instead aspiring level designers forgot to hint on where the tings are to be found, leaving the player to explore the map again and again everytime some magic happened to make things appear or disappear, and ultimately rely on some flying potion to collect the last tings somewhere in the top of the map where regular platforming just can't let you get them.

Mais j'étais curieux l'autre jour de voir un peu à quoi pouvait ressembler les niveaux intégrés dans l'édition "Rayman Forever" -- niveaux de fans repris sur galette. Sont-ils du même tonneau que les missions impossibles de Mario Maker ?


Pas exactement. Pas ceux que j'ai vu, en tout cas. Par contre, il y a usage abusif de la "potion magique qui te donne des aileuus", présente presque systématiquement, et contrairement aux designers de Ubisoft, qui prenaient soin de guider le joueur vers la suite du niveau une fois qu'on a ramassé le "ting-qui-fait-apparaître-un-pont-quelque-part", on risque ici de devoir ré-explorer tout le niveau chaque fois qu'on entend la petite fanfare qui annonce que quelque-chose a changé. Bof pour moi, donc.

Tuesday, December 15, 2015

The missing frame.

Why is my backtrace-recording code not properly recording backtrace ? And the true question is ... is it really not back-tracing correctly ?
  • malloc < new() < __gnu_cxx::new_allocator < Vector_base < std::vector ... " is missing __gnu_cxx::new_allocator.
  • "malloc < new() < TestBasicScript() < main() is missing TestBasicScript.
  • malloc < GameScript ctor < TestBasicScript is complete. good.
  • malloc < operator new  < GameScript ctor < TestBasicScript < main is missing the ctor.
Having a look at the disassembled code explains already some of the things. For instance, remembering me that neither the constructor calls operator new, nor the operator new calls the constructor. No. The function constructing an object, TestBasicScript, calls both the "operator new" (_Znwj) and then the constructor.

Next interesting thing, operator new is systematically the function whose caller is missing. That can be explained if operator new itself is not creating some stack frame.

void TestBasicScript() {
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   56                      push   %esi
   4:   53                      push   %ebx
   5:   83 ec 20                sub    $0x20,%esp
/home/pype/DS/tests/BasicScript.cpp:6
  BufferReader *ir = new BufferReader("print \"hello\"\nend\n");
   8:   c7 04 24 0c 00 00 00    movl   $0xc,(%esp)
_ZN12BufferReaderD0Ev():
   f:   e8 fc ff ff ff          call     _Znwj
  14:   89 c3                   mov    %eax,%ebx
  16:   c7 44 24 04 00 00 00    movl   $0x0,0x4(%esp)
  1d:   00 
                        1a: R_386_32    .rodata
_ZN11InputReaderD2Ev():
  1e:   89 1c 24                mov    %ebx,(%esp)
  21:   e8 fc ff ff ff          call   _ZN12BufferReaderC1EPKc
_ZN12BufferReaderC2EPKc():
  26:   89 5d f0                mov    %ebx,-0x10(%ebp) 

Sunday, December 13, 2015

Emunit-testing

I am stuck with bugs in the two approaches I planned to use to allow shop-for-bonus in-between levels. Kind of bugs that are difficult to track with regular debugging, and that may even crash the emulator or the debugger itself.

http://problemkaputt.de/gbatek.htmSo it's time for me to start doing more professional testing on my code base, dropping rendering and things alike so that I have more control on memor allocation and objects lifecycle. For that I need that access to NDS registers can still be performed although it will be a regular x86 program running under linux. I thought I could use a custom linker map to force the declaration of a regular area where the registers are expected, which you use with -T linker-script-file. But being sure that all of C++ sections will remain in place might be harder than creating a custom layout for an operating system kernel.

http://sourceforge.net/p/dsgametools/hg/ci/c3d54a0c1b14173735513f8227cdd2f8f93d6645/Hopefully, the memory map is somewhat compatible with the default locations for a 32-bit linux process. A few mmap calls and I can have valid read/write memory mapped at the place where NDS registers are expected. That should be all I need.

I will need to track what memory area are still alive after the end of a level, to know whether some could still reference dead objects and lead to memory corruption. I'll need too, to know who allocated such block so that I can understand their nature and purpose. I remember of Tim Robinson explaining how he embedded a call trace leading to *alloc. I'll try and do the same. It's not quite completely working at the moment.

Friday, December 11, 2015

Handmade Hero

I discovered "Handmade Hero"'s channel promotion. It sounded like something I'd love. A game from scratch, coded day after day. Still, I'm disappointed. So many videos on side topics such as font alignments, debugging techniques, etc. Titles doesn't say a lot on what is happening, we see text editors most of the time during explanations. Things could easily be clearer to follow. Sessions are quite long compared to Bisqwit's coding speedruns, and to be honest, Youtube lists doesn't get as useful as a cloud tag.

Ç'aurait dû être une super découverte. Un gars qui veut faire son moteur de jeu lui-même, "à l'ancienne" ... qui s'y met un brin tous les jours. J'ai essayé quelques vidéos, certaines qui m'ont fait réfléchir, d'autre nettement moins.

J'en viens à me dire que la vidéo n'est pas un bon média pour ce genre d'information. Trop difficile de passer d'un sujet à l'autre. Presqu'impossible de retourner sur l'élément précédent. Une base de code de la taille d'un moteur de jeu, il va y avoir une énorme partie qui n'apparaît pas à l'écran. Je doute fort qu'il soit possible de garder ça en tête au rythme d'une heure et demie par jour.

You see, that's why I keep opting for homebrews. True gaming hardware makes it easier to focus on what makes a game a game. Sure I can write a pitch bender or a volume adjuster. But I like it so much more when I have mixing hardware instead.

Wednesday, December 02, 2015

Spherical Goats

C'est une histoire qui commence dans les années '90 lorsque mon frère fera tourner pour la première fois le jeu "Spherical" sur C64. Il s'agit d'une sorte de puzzle-platformer, où le joueur (incarné par un sorcier à barbe blanche) transforme son niveau (en créant ou détruisant des blocs) pour guider une boule vers la sortie.

Je n'y aurai pas joué beaucoup, puisque c'est systématiquement sous le nom "Wizball" que je le cherchais dans la pile de floppies récupéré de chez nos copains qui quittaient l'ère 8-bit. Je ne l'aurai jamais vraiment reconstruit non plus: un jeu de puzzle qu'on fait soit-même, c'est rarement aussi intéressant qu'un jeu de plate-forme qu'on fait soit-même. Pourtant, dès les premières images, j'ai été subjugué par le genre, tout nouveau pour moi.

I wish I had more opportunities to play Spherical on my C64, back in the '90s. I loved that grid-arranged puzzles with simple mechanics. I loved how it combined with self-moving objects to bring an extra layer of deepness compared to what pencil-and-paper puzzles could provide. Nowadays, I guess I could run Escape Goat 2 and support Ian Stocker in his indie game adventure. There is a lot of very interesting game mechanics at work, and a nice extension to the classical push-me, blow-me blocks I'd put in a Logic Labyrinth.

C'est une histoire actuelle, aussi puisqu'elle parle de Escape Goat 2, un jeu indé sous le feu des projecteurs de RealMyop et CoeurDeVandale. On y retrouve ici aussi niveau très en carrés, avec des blocs qu'on va déplacer pour se frayer un chemin ... même si c'est beaucoup plus indirectement.

En fait de sphère, la chèvre manipule ici Henri la souris, partiellement immortel, capable de déclencher pour nous les interrupteurs des nombreux mécanismes, d'arrêter les tirs d'ennemis etc.

Je note
  • des blocs-caisses qui se font détruire par les blocs qui tombent, mais qui peuvent aussi retenir les même blocs alors qu'il n'y a rien sous la caisse.
  • des interrupteurs qui sont activés par n'importe quoi, et d'autres seulement par le joueur.
  • des blocs-circulaires qui continuent sur leur lancée une fois mis en route, comme dans Johnny Biscuit d'ishisoft.
  • des blocs que les tirs des ennemis font exploser ou brûler de proche en proche.
  • des blocs qui glissent et écrasent les monstres façon Boulder Dash.
  • du sol qui s'écroule ... de proche en proche.
  • des portes à sens unique ... avec ou sans fermeture automatique.
  • un monstre qui peut pousser les blocs que le joueur ne peut que faire tomber 
Côté graphique,  j'aime beaucoup la présence de gros engrenages, à l'arrière du décor, qui se mettent en mouvement quand les interrupteurs provoquent le déplacement en cascade des blocs mobiles.