Showing posts with label cxx. Show all posts
Showing posts with label cxx. Show all posts

Thursday, June 06, 2024

RTTI vs getSubClass()

I was doing OS development when I started learning C++, and then I went on with DS development. In both case, the amount of memory you're willing to devote to the code of your program is limited. I remember I heard of -fno-rtti on the osdev wiki and thought "well, yeah, that makes sense". I'd rather not have something I know almost nothing about making key decisions on my objects when I have an instance of SomethingGeneric and want to turn it into SomethingMoreSpecific to use its advanced API for any purpose.

Here and now, I had something where I'd be in a similar situation, but there's actually just one way it could be more specific. I had added SomethingGeneric::isSpecific() virtual function and used it to see whether I should reinterpret_cast<SomethingMoreSpecific&>(myGeneric) here and there. Then the colleague who reviewed suggested it might better be a dynamic_cast<SomethingMoreSpecific&> instead ... or we could have SomethingGeneric::getSpecific() instead. and, well, yeah. I like that.

That wouldn't work with SomethingImplementedWithOpenGL vs. SomethingImplementedWithDirect3D vs. SomethingImplementedWithBlitterAndCopper, but for this use case, it's just fine. I don't have more weight on the virtual table ... I don't depend on unknown run-time ... I don't need to add exceptions catching in case of std::bad_cast ... So long, dynamic_cast: I won't need you, after all.

Monday, May 27, 2024

Compile-Time Regular Expressions

Well, guess what: there's a C++ header/library out there that allows regular expressions compiled at build-time. And I've got state machine parsing code that could use a little performance boost, especially if I envision GBA support rather than NDS for some future release.

#include <ctre.hpp>
#include <optional>

std::optional<std::pair<std::string_view, std::string_view>> 
match(std::string_view sv) noexcept {
    if (auto re = ctre::match<"state([0-9]++) *-> *state([0-9]++) *on (hit|found|event|fail)">(sv)) {
        return std::pair{
            std::string_view(re.get<1>()),
            std::string_view(re.get<2>())
        };
    }
    return std::nullopt;
}

Is what the siscanf(ln, "state%d -> state%d on %[hitfoundeventfail]%n") test for state transitions would look like, for instance. Some things will be cheaper, like testing explicitly for one of the 4 transition types rather than getting any garbage word you could craft with their letters and testing if (strcmp(reason, "event")) afterwards. 

Other things will require more code, like converting re.get<1>() into a digit as a post-processing step, since regexp do solely text matching and extraction, no text-to-number conversions. 

The code generated was quite convincing on x86_64, I'm a bit more suspicious about its ability to improve performance on 32-bit ARM processor, given how it requires 9 instructions to match every single character of the "state" constant string, for instance. If it helps for speed, it might have a significant impact on code size...

It seems to build even for GCC 10.2.0, the latest I installed from devkitpro, but not for the one used to build SchoolRush ...

Saturday, September 30, 2023

Un p'tit coup de marteau ...

Mes dernières sessions avec AnimEDS s'étaient toutes soldées par des "guru meditation". Peu de travail perdu, heureusement, le problème se produisant généralement soit juste au début, soit juste après une sauvegarde. L'impact sur ma motivation à continuer à animer mes p'tits persos en prenait quand-même chaque fois un coup: j'aurais pu effectivement perdre un travail précieux.

Alors j'ai noté de faire du debugging de tout ça. Il y a un bail. D'abord refaire une version précise de l'animateur (rH2021) et garder le .nds et son .elf à côté des fichiers re-générés à tout bout de champ quand je bricole du homebrew, histoire que quand le problème se présente console en main, je puisse effectivement utiliser la valeur du registre PC pour retrouver un numéro de ligne dans le code. C'est le cas depuis Juin.

I can't help wondering whether this is worth translating. It's another epic (?) showdown between me and my code to see who's got the StrongARM. But well, it has been bugging me since January, crashing the animation editor almost every time I used it. I'm just lucky I never lost anything important but motivation to work on some cute things over the evening. The "guru meditation" screens I got during the first half of the year were almost useless: the AnimEDS build on my 'lime' DS was so old I did not have the matching .ELF file for my debugger anymore. Believe it or not, the RealLife (tm) has turned so intense that I even had to write down an agenda check list with "rebuild ; keep .elf apart ; upload .nds to lime" to actually get it done.

Pas de chance: c'est un de ces bugs où l'adresse ne dit pas grand-chose parce qu'on est a suivi des pointeurs de fonctions qui ne voulaient rien dire. Mais! Bonne nouvelle, avec les nouvelles animations pour l'Appleman, le bug est plus facile à reproduire: il suffit d'essayer de copier une frame d'animation juste après avoir chargé la première animation du fichier dans l'éditeur.

Alors j'ai ressorti mon émulateur et mon débuggeur et là, bonne nouvelle, ça foire aussi dans l'émulateur. Différemment, mais ça foire, donc c'est débuggable. Et là, j'ai galéré pendant des heures ... Des structures qui ne veulent rien dire, des vptr complètement à l'ouest ... Il faut dire que pour encoder la ligne du temps utilisée par l'éditeur, j'ai pris une std::list de std::pair de classe dérivées. Bref, on se perd sous les couches de templates, de membres dépendant de l'implémentation et d'optimisation du compilateur qui prend un malin plaisir à rendre inaccessible les variables dont on aurait besoin.

With that done, I wasn't much more lucky. It's one of those bugs where you try doing a virtual function call on something that isn't truly an object and thus end up in the middle of nowhere, especially where memory contents doesn't match any valid opcode. Hopefully, while trying to get the crash to write down registers values, I realised that the bug was actually easy to reproduce (just open one animation and try to copy the frame before selecting any frame) and a bit later, that it also happened with the same file in my emulator. At least I could save the evening where I navigate DS memory to reconstruct objects on paper this time.

J'allais jeter le gant puis un soir j'ai noté dans mon calepin "fait du débugging indirect en surveillant les constructeurs". Bah oui: la variable aura beau avoir été optimisée, il faut bien qu'il soit construit à un moment où à un autre, le TIFrame qui explique pourquoi j'ai du n'importe quoi dans les registres au moment de copier cette première frame.

Sauf que ... non. Les "TIFrames" sont construites vides, puis au fur et à mesure que l'AnimationParser traite la liste de commandes destinées à GEDS -- le moteur de jeu -- il complète les coordonnées, les numéros d'images, etc. J'étais sur le point de laisser tomber (traduire: réécrire tout avec ma propre classe 'liste' plus propice au debugging) quand je réalise que je peux "figer" l'afficher d'une ou de TIFrame dont je capture l'adresse à la construction, et suivre leur évolution jusqu'au bug (l'animation en question ne contient en réalité qu'une frame et une commande de contrôle). Et là, surprise, tout va très bien (Mme la Marquise :)

 It did not save navigating memory altogether though. The way gdb handles std::list and std::pair combined to the amount of variables that were actually "optimized away" when they would be critical to have around still turned that debugging session into some guru meditation. I first thought that it was because of some incoherent animation instructions into the animation itself, but inspecting how the animationParser processed them shown everything should be fine. Yet, if I tried to see what frame was triggering the bug, all I'd get was garbage non-sense. I was about to replace all that std::* by some pype::* when I realised that I could just break on constructors of the contained TimeItems to see what the list contained.

That did not work either, unfortunately, because when TimeItems are added to the list, they are "blank" items that will be modified by the yet-to-come UPDATE animation commands. What did help, was creating some static watches at addresses discovered in a constructor-breakpoints so that I could look at the state of my list just before the offending call. That and realising that the list was just fine, thanks, but that I was using an iterator that might not have been updated since the previous list had been trashed and replaced by the new one :P

Une seule explication restante: c'est l'itérateur qui devient invalide au bout d'un moment. Je sors mon cahier A4 histoire de me faire une map UML de tous les bouts de code impliqués et c'est bien ça: quand on choisit une animation à éditer, la liste est vidée, mais l'itérateur reste inchangé, ce qui est invalide.

Ah oui. Vous vous demandez "pourquoi le marteau" ... et vous n'avez pas Thor. C'est à cause de cette blague d'ingénieur où un consultant rentre une facture de $100000 pour une intervention et ça rouspète chez le client parce que "vous avez juste donné un coup de marteau!". Et le consultant de reconnaître qu'il a fait une erreur et de préparer la facture suivante

  • 1 hammer hit: $10
  • knowing where to hit $99990

(bon, c'était vraiment une grosse machine très chère qu'il fallait dépanner). Bin c'est un peu la sensation que ça me fait sur ce bug-ci. Sauf que je ne vais pas recevoir des cents et des mille et que je ne devrai pas les débourser non plus :P

 

Friday, September 08, 2023

operator sockaddr*()

J'ai passé un peu de temps cet été dans du code third-party qui avait un autre dialecte c++ que moi. En particulier, ils aimaient bien faire des objets-wrappers métamorphes. Prenons par exemple une adresse réseau. C'est pénible: on peut l'avoir sous forme ASCII ou dans un entier 32-bit (ou un gros blob si on est en IPv6). Et de toutes façons, pour s'en servir il faudra qu'elle soit emballée dans un sockaddr, seule connue de l'API socket, qui reprend un identifiant de 'famille de protocole' et un numéro de port.

Tout ça semble justifier une classe NetAddress qui aurait des constructeurs NetAddress(std::string fromUserInterface) et NetAddress(uint32_t fromProtocolMessage). Très bien. On peut aussi en faire en réalité un wrapper de `struct sockaddr_storage`, et remplir les différents champs au moment de l'appel. Avec éventuellement un NetAddress.setPort() pour faire bonne mesure.

Mais ce n'est pas ça qui va changer le fait que connect() et bind() travaillent exclusivement avec des sockaddr. Là où j'ai été surpris, c'est qu'au lieu d'un NetAddress.getSockAddr(), j'ai eu droit à

operator sockaddr*() {
    return reinterpret_cast<const sockaddr*>(&address);
}

Séduisant a priori. Elégant, même. Mais à l'usage (et surtout à la lecture), ça s'est avéré être un fiasco. Je tombais sur du code du genre connect(toServer, myServerAddress, options) suffisament loin de la définition de myServerAddress et je zappais qu'il ne s'agissait pas d'un sockaddr classique, mais de l'objet emballant. Particulièrement piégeux quand on essaie de découvrir d'où provient l'exception InvalidArgument que rien dans le code de connect ne semble pouvoir générer.

Et ç'aurait pu être encore pire si myServerAddress avait été un pointeur vers une NetAddress ... Là, on aurait dû écrire connect(toServer, *myServerAddress, options), parce que c'est un NetAddress-même que le compilateur a appris à "traduire" en sockaddr* à l'aide de l'opérateur. Pas un pointeur de NetAddress. Perturbant au possible quand on se souvient qu'en C on aurait écrit connect(toServer, &server_address, opts);

...

Sinon, vous, l'été, ça a été ?

Tuesday, April 18, 2023

std::unique_ptr<>

Okay, it shouldn't be that hard to have a variable that gives access to an object RAII-style while allowing late initialization: I should use std::unique_ptr

RAII is neat. Despite having an impossible-to-use-and-remember-name (don't be surprised if I use 'Rabi' instead ;), it means that you spend less time catching/rethrowing exceptions, less time checking return codes (because you used exceptions) and can think of your software as something as reliable as a building instead of some Jenga game. Want to have some code dealing with some file, but don't want to forget closing the file handle if anything goes wrong ? Simple.

  • have a RabiFile class that encapsulate your file handler
  • have the ctor of RabiFile do *everything* that's needed to be working on the file
  • have the destructor always going to a clean state.

As soon as you've written something like RabiFile music("forest.xm", RabiFile::RO), you're good to go

  • either your RabiFile exists and all its methods are now valid
  • or you failed to create it and an exception has been thrown, taking you out

So with that approach, you'll never write any music->open("underwater.xm") nor any music->close() anywhere. That makes writing of constructors a *bit* more complicated, I admit, especially those for objects that contains some RabiObjects: they have to catch exceptions and rollback any 'personal' resource acquisition they performed because nothing invokes destructor on objects that haven't been fully constructed. If your constructor fails with an exception, it's up to your constructor to ensure it leaves no mines behind.

But that usually don't happens a lot. It does happen when I stretch the RAII fashion to long-lived objects like GameLevel that has LevelMap and SpriteSet(s), or to GobState that has GobTransitions, though. But Rabi* is mostly for temporary things that need us to hold something while something else.

There's one common drawback to both, though. Because my objects have to be allocated on the stack to benefit automatic cleanup on failure, it is tricky to benefit from polymorphism. Say I should either create an instance that reads the script from a file or an instance that reads it from a buffer received over WiFi by runME ... I can't just replace BufferReader reader(...) at the head of my function by


loaderCode() {
   if (fromFile) {
       FileReader reader(whichFile);
   } else {
       BufferReader reader(whichBuffer);
   }
   DoStuffWith(reader); // no such reader, dude.
}

because there, the condition-dependant objects are no longer valid when I then want to use them. And migrating the DoStuffWith into the condition will soon turn unpleasant as well because it is not DRY. So instead, I can have


loaderCode() {
   std::unique_ptr<InputReader> reader;
   if (fromFile) { 
      reader = std::unique_ptr<FileReader>(new FileReader(...));
   } else {
      reader = std::unique_ptr<BufferReader>(new BufferReader(...));
   }
   DoStuffWith(*reader);
}

Granted, that will not allocate the object on the stack but at least it guarantees that the created object gets deleted whatever happens during DoStuffWit(*reader). Given the size of the NDS stack, it might not be a bad move.
Maybe I could have done it with auto_ptr instead. It seems it was the way to do it before C++11 came, declared auto_ptr obsolete and unique_ptr as being the way to do this instead.

A few things to remember when working with unique_ptr

  • you must use std::move(reader) if you intend to return the unique pointer as function value
  • you must use std::move(aswell) if you want to capture some unique pointer you've received as a member of a new uniquely pointed thing

  •  

Saturday, December 18, 2021

Ludricous Speed

 There's another bug with my modified version of libntxm. It has been around since at least early School Rush prototypes and possibly even Apple Assault. If you let the player spin for too long, it suddenly starts to play things at ridiculous speed that makes the song barely recognizable.

I had the Dreams demo running with ARM7 debugger plugged in for another reason (undefined instruction) and it started speeding the song up so I dropped all my kitchen stuff and jumped on the CTRL+C to be able to dig that later. And later is now.

One symptom of the issue seems to be that the tick_ms variable is now way larger than the MsPerTick() property of the Song.

Every time the playTimeHandler() is invoked, it recomputes how many milliseconds elapsed since last call, and here I get the value of 1. It won't get out of the issue because there's nowhere we could read tick_ms = 0. Instead, the amount of time expected is substracted from the time elapsed so far, so that 'extra milliseconds' are deduced from the next 'tick'.But watching that tick_ms evolve, it is properly decreasing. It has not just been gone wild. It got corrupted from something else.

It doesn't tell me what goes wrong in the soft, unfortunately, but at least, it gives me a way to make sure players won't experience it. If I cap the tick_ms to 2*MsPerTick(), I shouldn't have issues.

Note how buggy tick_ms is close to last_ms ...

Note how unlikely it is that the player has been running for 43509 seconds. I wasn't even running it for one hour! And when it eventually occured again, lastms was 43494543, or something alike (decimal, not hexa).

Seeing some more code featuring 'compute over 100' now makes much more sense. 100 times higher than 43 million, we're quite at MAXINT for an unsigned. Plus, if you've got an unsigned, comparing -t to something might very well produce something that is compiler-dependent. Likely, there might be a switch to produce a warning for that.

Il y a un truc qui me chagrinait, avec School Rush: au bout d'un certain temps la musique partait en vrille. Le problème se posait uniquement au bout d'un temps assez long, du coup, je n'avais jamais eu l'occasion de prendre le temps de chercher le problème. Mais hier, j'avais un débuggeur ARM7 prêt à fonctionner, un émulateur branché dessus avec le code qui 'là-normalement-ça-devrait-marcher' pour un autre soucis et j'étais en cuisine ... donc ils ont tourné pendant longtemps sans intervention ... assez longtemps pour que le problème de School Rush se produise. J'ai bondi sur mon ordi pour interrompre tout ça et débugguer enfin cette histoire.

Au premier tour, tout ce que j'ai pu dire c'est que "bin c'est parce que tout d'un coup, on découvre qu'on est en retard de plus de 40 millions de microsecondes pour passer au 'tic' de musique suivant". J'avais espéré qu'il s'agissait d'une déviation ou quelque-chose de ce genre, j'avais mis quelques 'break if...' dans ddd mais quand le problème s'était finalement re-reproduit (bonne journée, ça), on était passé de quelques millisecondes de retard à plus de 43 millions, comme ça, sans-transition-à-vous-les-studios. J'en venais à me dire que 'tant pis, je vais juste mettre un patch qui limite artificiellement ce délai' puis je tombe sur l'implémentation de la lecture du temps à partir du hardware qui cache une division par 100 ... et 100 * 43 million, on approche du plus grand entier 32 bits. Là, ça pue le bug de conversions arithmétique dû à une soustraction mal calibrée... ça je pourrai corriger pour de bon.

Thursday, April 29, 2021

UsingSprites was a mistake. Wasn't it ?

 While refactoring the animation editor, I tasted a new design pattern as an alternative to the usual Singleton pattern: the Using* anti-pattern. The idea was simple: for every item that needs to be there one and only one time, there is a class who has the sole responsibility for holding the variables describing that item. Let's take for instance the hardware sprites of the Nintendo DS. We have an array of 'sprites', an array of 'rotations' and a z-order list.

The Using* anti-pattern said "these 3 should be static members of UsingSprites, and any code who needs to access them (read or write) will derive from UsingSprites". What it guarantees, is that if something messed up with * global, shared state, then that something is in one of the classes Using*.

The one nice thing, is that code in those sub-classes can access the array quite directly: the location of UsingSprites::sprites is stored next to the functions that need it and it just needs a ld r*, [pc, offset] to be ready to use it. No additional pointer dereferencing required. This is the very same as accessing e.g. the address of a constant string (ARM thumb code doesn't seem to have mov reg, imm32 instruction).

What I hadn't foreseen when I decided to generalise that to the whole game engine, is that a) there would be so many Using* for simple classes such as an animation and b) that they would all introduce a virtual table where the pointers to virtual methods for that class are stored.

The issue with that is that when you have multiple inheritance, you need one vptr (pointer to a virtual table) per super-class. That means every GobAnim instance must dedicate 5 full-sized pointers to virtual tables in addition to the 'real' data of the object.

Hopefully, it turns out to be a worst-case. GobState has 4 superclasses and most of the state-machine only one or two.

When looking at that inheritance graph again, I wonder if Using* is the right one to blame. iReport is there just to bring action/step/report/diagnose functions into the local namespace, but they are actually just static functions manipulating no particular state. Same goes for iAnimUser. iReport is so omnious that it could really be dropped and the functions just promoted to global namespace just like printf. iAnimUser could just be a namespace and not mess up with the inheritance, as it just provides constants and helper inline functions with, again, no dedicated state.


Thursday, January 07, 2021

Titanic death by a worm

Petit retour sur une lecture de Cactuceratops contre Conquer (bad fur day), qui cherche à comprendre pourquoi les voix du personnage principal 'rippées' hors du jeu sonnent différemment. Pour info, c'est un des premiers jeux à utiliser de la compression MP3 pour les échantillons vocaux. Au milieu de ses investigations, elle met le doigt sur un fait intéressant: certains blocs de son dans le MP3 sont marqués 'copyrightés' et les autres pas. Si on retire les blocs 'copyrightés', le son est parfaitement normal.


Le bit de copyright est justement celui qui distingue de l’entête le plus fréquent celui qui apparaît seulement pour la voix de Conker. Son dernier octet est en effet égal à 0xC8 plutôt qu’à 0xC0 (les quatre derniers bits valant 1000 au lieu de 0000). 
Et ce quelque chose, c'est les mouvements de la bouche de Conker, ce qu'on appelle le “lip-sync”. Ces 9 octets ajoutés viennent de l'outil développé par Mike Currington pour faciliter le travail des animateurs et rattacher ses nombreuses expressions aux articulations de sa voix
En clair, les développeurs de chez Rare ont improvisé un fichier avec entrelacement audio/data en tirant parti du fait qu'ils avaient leur player "à eux" et donc l'opportunité de travailler avec leur propre solution de multiplexage. Et après avoir vu des formats .LBM qui embarquent leur aperçu dans une section .TINY, des .MODs avec des e-mails planqués dans le 6eme instruments, etc. Je me dis ... "c'est bien le genre de hack auquel j'aurais eu recours, si j'avais été dans la boîte".

En fait, je pense bien que j'ai déjà réellement voulu utiliser un truc pareil. Retour en '99

Début de l'année, je me suis acheté mon premier PC perso: un AMD K6-II, premier à avoir je ne sais plus trop quelles extensions 3DNow! auprès d'un étudiant quelques années au-dessus de moi, ci-dessous Gino. Comme il avait voulu attirer mon attention dessus, je fais au téléphone "oui, j'ai vu. Comme je code en assembleur, c'est le genre de chose que je note tout de suite". Et lui "ah!? Tu fais de l'assembleur! Tu vas à la Inscene, alors ?"

Et de fil en aiguille, on se retrouvera avec mon frère dans la voiture de Gino avec nos tours (plus ou moins grosses), nos écrans (pas plats), le synthé de mon frangin (iirc), des sacs de couchage et quelques blocs-notes. C'est là que sera présenté Crazy Brix et El Ritmo Latino.

Pendant que mon frère participe à toutes les compétitions-surprise de musique et graphisme, que Gino fait du photoshop et retouve des connaissances, moi, je suis plongé dans le code source Visual C++ de notre Grrand Prrojet: "Titanic Death by a Worm" qu'on avait largement pré-produit depuis la fin des examens.

Je m'étais déjà bien lâché sur les p'tits personnages, contrairement aux Worms qui sont tous les mêmes (mais auxquels on donne une personnalité), nous on leur a donné à tous un look perso et des attaques spécifiques.

Mon frère avait fait un .IT avec toutes les actions prévues dans le script (ou il avait fait le script dans Impulse Tracker, en fait. Je ne sais plus trop). Restait à trouver comment aligner les actions des pixels avec le replay du module. Eh bin, puisque dans un .IT, y'a moyen d'utiliser plus de 26 effets différents (arpeggio, glissando, vibrato ...), on aura qu'à se réserver un effet qui ne changera pas le son, mais qui pilotera l'avancement dans le script visuel.

Bon, par contre si vous avez déjà fait une game jam, vous devinerez la suite, hein. Avec à peine une coquille où on sait passer en 320x200 dans DirectX (merci Gino) et charger mikmod.dll au moment de monter son PC et qu'on bloque sur le choix de comment créer des classes dérivées à partir d'une classe de base et d'un script texte ... eh bin, je me serai beaucoup pris la tête, et il n'y aura jamais de pworms.exe ...

edit: et pourtant, le projet pworms n'était pas trop démesuré comparé à "Kid, show me your dreams" et "Comin' to my Haus" (ne parlons même-pas de l'intro du jeu polycosmos, hein ;)



Thursday, February 27, 2020

fgets

J'ai apparemment laissé dans un commentaire de revue "fgets doesn't guarantee you have a terminating \0 on oversized lines". Il faut bien reconnaître que c'est le genre de farce auxquelles il faut s'attendre de la part de la bibliothèque C standard. Mais là, à y regarder une 2eme fois, j'avais tout faux.

fgets(ptr, size, stream)  reads in at most one less than size characters from stream and stores them into the buffer pointed to by ptr.  Reading stops after an EOF or a newline.  If a newline is read, it is stored into the buffer. A terminating null byte ('\0') is stored after the last character in the buffer.
C'est presque le mieux qu'on puisse espérer, non ? on lui passe un buffer alloué avec data[N] comme (data, N) et il se garde un dernier p'tit caractère pour la route le \0 de fin-de-chaîne. Que demander de mieux ? Non, en vérité, c'est de strncpy qu'il faut se méfier.

Mais de ma phase de déni/doute, je me suis quand-même refait un p'tit programme pour tirer ça au clair. Bin rien à redire. On a bien un \0 de terminaison à chaque coup (le programme ne lit que 16 caractères à la fois). Soit juste après le retour à la ligne (\n), soit en dernière position.

Seul cas où il peut être manquant: si il n'y avait rien à lire du tout. (auquel cas fgets nous renvoie un pointeur nul plutôt qu'un pointeur vers notre buffer, ce bon vieux data). Même une fin de flux sans fin de ligne ne le met pas en défaut.


Brave petit.

Toujours à vous demander ce que c'est que cette histoire de \0 ? vous pensiez qu'un ordinateur auquel on répond "Piet Burner" quand il vous demande "Entrez votre nom >" savait où se trouvait le début et la fin du nom en mémoire ? Bin pas franchement. Dans la majorité des cas, il a besoin d'un caractère spécial, en fin de chaîne, pour savoir qu'il a tout lu / copié / analysé / imprimé ... C'est le caractère de valeur 0 (pas le caractère ASCII en forme de 0 que vous rajoutez à la fin de votre fiche de paie) qui s'y colle. Enfin. Presque toujours. Il existe un obscur système d'exploitation dans lequel ce rôle était dédié au caractère '$'. Si, si...

Faites en sorte que le \0 soit manquant (en mangeant trop de pommes, en écrabouillant la tête de Yoshi ou en tapant le Konami Code pendant le chargement du jeu), et le CPU continuera "sagement" à manipuler "des choses" comme si c'était votre nom ... avec potentiellement la possibilité d'aller écraser l'emplacement de votre personnage sur la map, des bouts d'inventaires au le nom du prochain pokemon que vous allez rencontrer :P

Oh, et tant qu'on y est, j'utilise aussi régulièrement snprintf, bien que codant en C++. Elle a le bon goût de ne pas écrire plus de size caractères, terminateur \0 compris et renvoie la longueur de chaîne qui aurait été écrite si on avait eu la place. Longueur qui s'entend au sens strlen (sans compter le terminateur) et donc si on me répond que 16 quand j'ai passé un tableau de 16 bytes pour stocker le résultat, c'est déjà un sacrifice de données (bien vu, jigé ;)

(edit) Hélas, il aura fallu attendre Windows 10 pour que snprintf y marche correctement T_T (i.e. comme spécifié dans C99)

Tuesday, February 18, 2020

Don't send namespace do an inline job

Okay, granted, I'm not a namespace master. I know they exist, I know they are helpful, but I'm not using them a lot myself, and I'm never too sure how I should write things like namespace cp = Clicker::process;. I'm not completely sure whether going for using namespace ietf::http within HttpServer class is smart or silly.

Anyway. I had code in a .cpp I wanted to extract into a .h, for it contained assert helpers that should now be used in more than one test program. Unfortunately, when I started using it in my new program, I got the linker complaining that some assert_bool() function was defined in multiple .o files. That was the only one that was no template, unlike assert_equals() and its children. I then foolishly wrapped the whole thing into a namespace /* anonymous */{, so that things couldn't be reused in multiple areas.

But one instance of gcc got it wrong. You see, I'm not using that assert_bool() in all my .o, and that version of gcc, with its parano flags settings, thought it shouldn't keep going with dead code: assert_bool() wasn't used internally in that translation unit, and it couldn't be used from outside, as it was anonymous for the rest of the world.

What I should actually have done was to define the function "inline". Simply.

Monday, February 10, 2020

GameEngine design: script-to-code

Somewhere in 2009, I added support for actions triggered by the script-part of my game engine but implemented in C++. This is the cornerstone of "GEDS" game engine. This is how run-time monsters spawning and sound effects work. This is also how level load request are processed.

The design survived the years, so let's see how responsibilities are split.

  • Anything that will start invoking code upon game event (collision, new input, animation completion, etc.) is captured in a *Gun instance. The instance carries all the parameter needed for firing a specific kind of code. E.g. An instance of the LevelGun capture the specific level script to load. An instance of GobGun captures which type of monster to spawn, etc.
  • Configuring the *Gun instance happens at script parsing time. In other terms, most of the *Guns could actually be constant objects.
  • The c++ code instanciating an *Gun must be located from the name of the action on the script (with a using [action-name] ([arguments]) as [holster-slot-number] statement). That requires every type of "gun" to have its associated "factory" class, so that an instance of each "factory" can be registered into an std::map to be the link between actionname and the *Gun constructor.
  • Amount of support code for a new *Gun or a new *GunFactory is minimal. Each class have only one (virtual) method, either create() or shoot().
  • *Gun instance typically forget about what they 'shot' as soon as shooting is done. Often, the created object is registered as an animated item at the game engine, which will take care of running its code periodically. This may lead to the need for an additional "progress" object when there isn't any yet, like with the TrackSequence following the instructions (e.g. shooting more guns) as a sound track unrolls in the music player.
  • when processing script expressions, a palette of "guns" (the multi-slot holster, if you want) is received in addition to the set of variables accessible to the script. However, the transitions between states are the one capturing those palettes.
  • The GameScript is responsible for recording every *Gun instance created, so that they can all be reclaimed when the level is destroyed and we switch to something new.
  • There is one guns palette per "state machine file", with the constraint that all the states used by a single "character" in the game have to be described within the same file, this means that a "character" can only use up to 16 different sounds+special effects.
  • The GameScript has the ownership of all these palettes. Reusing guns from parent's palette is explicitly requested. The GameScript knows when to stop using a given palette for new transition and switch to a new/old one because it has seen input/end statements. There is room for improvement here.
Lots of guns. But anyone in GEDS can at most carry 16 of them at a time.
Clearly, the major limit comes from fixed-size palettes of gun, which itself comes from the byte-code nature of the (parsed) script expression where only 4 bits are dedicated to the storage of which-gun-to-use. This is perfectly ok for a Mario-like game, where the distinct number of actions per character remains low. I would definitely need to extend this (and other parts of the game engine like sprite memory management) if I was to create a run-and-gun game where you can pick up lots of different weapons, each requiring a different kind of amno sprite and sound effect.

Thursday, February 06, 2020

Attempting to reference a deleted function

Now, all of sudden, the C++ compiler decides to make copies of my exception class before throwing it. Well, apparently, it is allowed to do so. But for some other reason, that very same C++ compiler (wasn't gcc this time) decided that my classes shouldn't receive a default copy constructor, and complains that "my" code is "attemtpy to reference a deleted function" when throwing the exception.

Oh well.

Most of the code isn't new, and it has compiled with this very compiler with these very flags just an hour ago. But while working on it, I suddenly changed at least one thing: I made it so that all the arguments of the constructor for the base class (the one complaining) are now constants. I suspect that Mr-Smart-C++ decided that my code would be happy to have one CustomException in .rodata with the corresponding fields compile-time-ready and that it would then copy that into the area allocated for ExtendedException (child class) instead of "constructing" a new one from scratch.

Now, why wouldn't it create a default copy constructor ? Well, I introduced something new in the class, that is a std::stringstream. And it turns out this one has a 'deleted' copy constructor... this apparently propagates and makes the containing class considering it should flag the copy constructor as deleted as well.

Just when you thought you had mastered the language. Welcome to Wonderland ...

Friday, January 31, 2020

looser throw specifier

J'utilisais assez volontiers les descriptions de "quelles exceptions peuvent être produites par cette fonction" en Java. Nettement moins en C++. J'ai en tête que le compilateur n'était pas si fiable que ça sur ce point et que le jeu n'en vaut donc pas la chandelle. Au point qu'avec les derniers GCC, j'ai peut-être bien dû supprimer des throw(iScriptException) pour que le code de libgeds continue de fonctionner.

Mais il y a au moins un cas pour lequel ça semble fonctionner et qui mérite qu'on y réfléchisse: les destructeurs (et plus particulièrement les destructeurs d'exception).


class InvalidArgs : public LibraryException {
public:
    InvalidArgs(const std::string &detail) : 
        LibraryException(INVALID_ARG_SUBCODE, INVALID_ARG_NONSENSE, detail)
    {}
    virtual ~InvalidArgs() throw() {}
   // LibraryException has defined its own destructor unable to throw any exception.
   // we can only make this stricter when sub-classing it.
}; 

Update: Il y a une FAQ sur la norme ISO C++, qui traite justement de la question des exceptions dans les constructeurs et les destructeurs. On y explique que tout les mécanismes de la bibliothèque standard partent du principe qu'un destructeur ne peut pas lancer d'exception. Entre autres parce qu'en réaction à une exception, votre libc++ va identifier les destructeurs de tous les objets alloués sur la pile jusqu'au code du catch et les invoquer, mais libc++ ne sait généralement pas faire remonter deux exceptions à la fois.

Donc il ne faut pas laisser une exception sortir d'un destructeur, et ajouter un throw() à la déclaration de son destructeur est un bon moyen que le compilateur nous y force. En particulier si on définit ça au sommet d'une hiérarchie de classes, auquel cas le compilo va aussi nous avertir si on essaye d'assouplir le contrat pour faire passer plus d'exception hors d'une méthode-fille que ne le permettait la méthode-parent.

(merci à hg grep pour avoir retrouvé les anciennes lignes où j'avais des throw(xxx). C'est un bon complément mercurial à hg annotate quand on cherche quelque-chose qui a disparu plutôt que le moment où quelque-chose a été introduit.)

Wednesday, August 14, 2019

__cxa_begin_catch

Voilà un symbole à surveiller de près. Quand mon code C++ me fera des misères et que le débugger semble me téléporter d'un bout à l'autre du code sans passer par les blocs catch(), il pourrait bien être salvateur d'aller mettre un breakpoint sur la fonction du run-time responsable de démarrer le traitement d'une exception interceptée.

Dans la même série, _dl_runtime_resolve est une vraie plaie en cours de debugging (et
LD_BIND_NOW, sa némésis, est donc notre alliée), mais j'aimerais bien en savoir plus sur son fonctionnement.

Saturday, December 29, 2018

Wformat-truncation

Pretty impressive new feature of gcc/g++ available in the latest devkitarm.
Look at that.


MapWindow.cxx:377:23: warning: %i directive output may be truncated writing 
    between 1 and 2 bytes into a region of size between 0 and 9 [-Wformat-truncation]
snprintf(msg, 32, "@%i,%i : %s [%ix%i]",xpos, ypos, why,
                   ^~~~~~~~~~~~~~~~~~~~~
MapWindow.cxx:689:12:      report("editing meta-layer");
                                  ~~~~~~~~~~~~~~~~~~~~
MapWindow.cxx:377:23: note: directive argument in the range [8, 16] 
MapWindow.cxx:377:23: note: directive argument in the range [8, 16]
 note: 'snprintf' output between 32 and 42 bytes into a destination of size 32

- gcc detected that I'm calling that snprintf function with a value of 'why' that is actually the "editing meta-layer" string, which is 18 bytes long
- it understood that this and some numbers had to fit within 32-bytes output
- the format characters only take 9 bytes
- xpos and ypos are 16-bit values, needing at most 5 characters to be rendered on-screen
- 18 + 9 + 4 = 31. that's the case where all the numbers take only 1 byte. We have barely enough room to display the message (and its terminating zero character)
- 18 + 9 + 5 + 5 + 2 + 2 = 41. That's the case where we have all numbers using their maximum size. Note that GCC/G++ could guess that we'll only use numbers between 8 and 16 by looking at  blockop?16:8 argument!

Having such thing can be super precious when you know what's going on and want to harden your software. Hopefully, there is no overflow expected here. Only truncation. And since the purpose is to put a message on a 32-bytes wide line, I'm fine with truncation here. I'll have to tell that to the compiler the best possible way with some #pragma.

edit: oh, actually the compiler is even smarter than I thought: it can tell whether you checked for the return value and won't bother you if you handled truncation with an if (needed > sizeof(msg) - 1)
If the output was truncated due to this limit, then the return value is the number of characters (excluding the terminating NUL byte)  which would have been written to the final string if enough space had been available.
Oh, and by the way,
*snprintf() write at most size bytes (including the terminating null byte) to str.
So in my code where I want 32 characters on the 32-char-wide screen, I should have a 33-bytes buffer and pass 33 to snprintf.

Thursday, December 06, 2018

strtrololol


All of sudden, we realised that we had plenty of 'strtoul' that weren't checking they were actually receiving a number as argument. So we went for fixes and have a few more conditions that could trigger an exception.

If instead you suggest to go for stringstream, remember: it will expect either '.' or ',' depending on the value of LC_NUMERIC ... Maybe it isn't that bad I'm using mostly sscanf() in GEDS code :P Well, as long as I remember not to use %i when I mean %d, that is.

Tuesday, December 19, 2017

Refactoring de dingue!

J'hallucine. J'ai pris une de mes plus vieille classes -- InfiniMap, responsable à la fois du scrolling et des collisions sprites/map. J'ai renommé ça en "CommonMap", appliqué les changements partout sauf aux sites de construction. J'ai ensuite déplacé le code utile dans un nouvel InfiniMap, déclaré l'une ou l'autre méthode comme purement virtuelle dans CommonMap ... et recompilé.

I can hardly believe it. I picked of my oldest classes -- InfiniMap, that controls  scrolling and  -sprite-vs-world collisions -- and renamed it common map, everywhere but on lines of code building some instances. I then moved the "real" code into a new InfiniMap, mentioned a few ."pure virtual" markers in common Map, and rebuilt...

Et ça marche ! C'est du délire à l'état pur. Ok, j'avais une classe iWorld mais je ne m'attendais pas à ce que le truc ne réclamme aucune autre intervention. Nada.
Allez, demain, je déplace le maximum de variables membre.

And it works! I can't beleive it! (dott). I knew I had some interface class already, but I wasn't expecting it to work that easily, not requiring any fix or whatever !

Monday, February 29, 2016

Catch that!

Not so long ago, I was regretting that HandMade Hero was just giving debugging hints for weeks instead of talking of game engine and game design ... And I haven't posted myself anything about developing games but fixing and debugging since then. Hear this, fellow game developer. At some point, either you'll dig deep into debugging or you'll have to trash your project and move to another project, possibly from scratch.

Désolé pour ceux qui aiment les articles sur le dévelopement de jeu proprement dit. À ce stade-ci, soit je m'accroche et je continue à nettoyer le code malgré les difficultés techniques, soit je laisse tomber l'idée d'avoir un School Rush fiable. La bonne nouvelle c'est que peu à peu, les choses mystérieuses perdent de leur mystère et je progresse vers quelque-chose de mieux compris et mieux contrôlé.

Par exemple, si on lance une exception depuis un destructeur appelé lui-même suite au déclenchement d'une autre exception, la bibliothèque libstdc++ jette le gant et met fin au programme sans autre forme de procès.

Allez, soyez encore un peu patients. Je suis certains que vous trouverez que ça vaudra la peine d'avoir le niveau suivant chargé en moins de 2 secondes plutôt que d'attendre 20 secondes ou plus entre chaque essai.

how could we have terminate called ? 
Something was driving me nuts in those debugging/testing attempts: I couldn't get any exception properly reported, but instead the program terminated with the exception reaching top-level.
Of course, I asked for help on Stack Overflow, but the question got closed as noone could reproduce anything.


So here is how C++ could easily fail to catch your exception, and you know it happens when you see "terminate called recursively".  Don't let that "terminate called after throwing an instance of std::runtime_error (or whatever exception you've thrown) fool you. During stack unwinding, all destructors are invoked. If any of these try to throw an exception while unwinding the stack, we'll find ourselves into a recursive-terminate condition.


Oh, maybe you have the right to throw an exception and catch it before it leaves the stack frame of the destructor, but if the destructor-originated exception escape the exception-originated destructor call, then the exception management runtime apparently gives up and terminate the program.

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.