jeudi 11 juin 2020

C++14 - std::string_view veut jouer avec std::set

Petite expérience du matin (chagrin ?) : Comment faire un find sur un std::set en utilisant string_view ?

Revenons en arrière. Tout d'abord, qu'est-ce que std::string_view, et pourquoi voudrait-on l'utiliser pour faire un find ?

Vous avez peut-être remarqué que l'objet std::string est loin d'être gratuit. Non seulement les copies peuvent être chères (modulo COW et SSO, m'enfin bref), mais en plus le reste du monde conspire contre nous entre les chaînes codées en dur qui sont des const char *, et de nombreuses bibliothèques ne veulent pas en entendre parler. Voyez par exemple cette recherche dans un std::set : en passant un paramètre const char *, il se cache en fait la construction d'un objet std::string, et donc une allocation.

#include <set>
#include <string>
#include <iostream>

#include <stdlib.h>

int number_of_allocs = 0;

void* operator new(std::size_t size) {
  ++number_of_allocs;
  void *p = malloc(size);
  if(!p) throw std::bad_alloc();
  return p;
}

void* operator new  [](std::size_t size) {
  ++number_of_allocs;
  void *p = malloc(size);
  if(!p) throw std::bad_alloc();
  return p;
}

void* operator new  [](std::size_t size, const std::nothrow_t&) throw() {
  ++number_of_allocs;
  return malloc(size);
}
void* operator new   (std::size_t size, const std::nothrow_t&) throw() {
  ++number_of_allocs;
  return malloc(size);
}


void operator delete(void* ptr) throw() { free(ptr); }
void operator delete (void* ptr, const std::nothrow_t&) throw() { free(ptr); }
void operator delete[](void* ptr) throw() { free(ptr); }
void operator delete[](void* ptr, const std::nothrow_t&) throw() { free(ptr); }

int main()
{
  std::cout << number_of_allocs << std::endl;
  
  std::set<std::string> str({"a", "b", "c"});
  
  std::cout << number_of_allocs << std::endl;
  
  // Longue chaine pour éviter le SSO
  str.find("abceuhaoeuhaotuhaoutnohusaocamhamknomhucra,huh");
  
  std::cout << number_of_allocs << std::endl;

  return 0;
}

La punition est immédiate, le programme affiche 0, 3 et 4, parce que la chaîne dans le find a été convertie en std::string et a provoqué une allocation.

Si l'on s'amuse plutôt à passer un std::string_view, dans ce cas, le programme ne compile pas, car il n'y a pas de création implicite de std::string d'après une std::string_view.

  str.find(std::string_view("abceuhaoeuhaotuhaoutnohusaocamhamknomhucra,huh"));

Entre en scène la nouvelle surcharge de la fonction find, datant du C++14, qui prend un paramètre template qui doit être comparable avec une clé "de manière transparente", sans conversion nécessaire. Ah ah! Pile ce qu'il nous faut. Il faut juste donner au std::set un comparateur transparent, comme std::less, et cela fonctionne.

[...]
int main()
{
  std::cout << number_of_allocs << std::endl;
  
  std::set<std::string, std::less<> > str({"a", "b", "c"});
  
  std::cout << number_of_allocs << std::endl;
  
  str.find(std::string_view("abceuhaoeuhaotuhaoutnohusaocamhamknomhucra,huh"));
  
  std::cout << number_of_allocs << std::endl;

  return 0;
}

Victoire, le programme affiche 0, 3, 3, et a donc économisé l'allocation ! Mieux encore, la version où l'on passe juste la chaîne en const char * a également économisé son allocation, le std::string_view n'étant là que pour empêcher la conversion implicite.

Première conclusion : ajoutez des std::less<> à vos std::set et std::map prenant des chaînes !

Deuxième conclusion : pensez à creer un comparateur manuel si vous faites ce genre de magie avec vos propres objets

Troisème conclusion : passez à C++20 pour faire la même chose avec std::unordered_set et std::unordered_map. Je vous en recauserai peut-être quand j'aurai mis à jour mon compilo.

Références:
https://en.cppreference.com/w/cpp/string/basic_string_view
https://en.cppreference.com/w/cpp/container/unordered_set/find
https://stackoverflow.com/questions/9927856/how-to-use-operator-new-to-count-number-of-times-of-dynamic-memory-allocation
https://stackoverflow.com/questions/35525777/use-of-string-view-for-map-lookup

jeudi 7 mai 2020

Lanternes en vidéo

Une lanterne posée sur le sol, c'est rigolo, mais une lanterne qui tombe du plafond, c'est encore mieux ! Voici un rendu rapide (enfin, c'était la modélisation qui était rapide, le rendu, lui, a quand même pris 6 heures) de la lanterne du post précédent qui chute et rebondit sur le sol. Il a suffi de laisser faire le moteur physique de Blender, et le tour était joué. On admirera les effets de lumière sur le sol quand la lanterne le percute.

Une lanterne

Petite scène rapide avec une lanterne. C'était finalement beaucoup plus facile que prévu : simplement se mettre une petite loupiote dans le cube (aïe !) et utiliser pour le cube un shader translucide coloré. Le tour est joué !

Il y a probablement moyen d'en faire une vidéo...

dimanche 3 mai 2020

Ça y'est, j'ai mis le truc dans le machin !

C'était sport, mais j'ai enfin réussi à ravitailler en vol avec DCS World et le Mirage 2000 ! Comme quoi, l'entraînement finit par porter ses fruits. Les trucs qui m'ont bien aidé :

  • Le trim ! Quand l'avion est bien trimé horizontalement, les corrections sur le manche se font à peu près autour de la zone neutre, ce qui est bien plus reposant pour le bras, et semble plus intuitif.
  • Gérer sa manette des gaz en pompant autour d'un point d'équilibre correspondant à peu près à la vitesse cible. Pour accélérer, ajouter un filet de gaz, puis revenir au point d'équilibre. Pour ralentir, réduire un peu puis remettre les gaz. Cela aide à garder un petit différentiel de vitesse, et accessoirement à éviter de se prendre un réacteur en pleine poire.

Dans la petite dizaine de secondes pendant laquelle j'ai réussi à rester connecter, j'ai pu pomper environ 400 kg de fuel, ce qui est vachement généreux de la part de DCS. Dans la réalité, c'est plutôt 10 minutes pour faire le plein, et rester stable tout ce temps me semble bien au dessus de mes forces !

mercredi 29 avril 2020

Inverse kinematics et tourelle de tank

Petite démo d'une simulation inverse kinematics pour préparer ma tourelle de tank. Par rapport à mes précédents essais, j'ai ajouté des contraintes de rotation afin que la tourelle ne puisse que tourner sur l'axe Z, tandis que le canon est limité a 40° dans l'axe Y. Je peux ensuite animer l'ensemble à l'aide d'une contrainte IK qui va faire pointer le canon vers un os (que je fais marcher sur un chemin). Le résultat, une belle (enfin, une moche) tourelle !

Prochaine étape : lui faire tirer des projectiles.

lundi 27 avril 2020

Encore 7 jours pour essayer tout un tas de modules gratuits sur DCS World

Depuis 1 semaine, DCS propose la plus grande partie de ses modules gratuitement, pour 2 semaines (ce qui nous laisse donc 1 semaine, si mes calculs sont justes). C'est l'occasion de tester tout un tas de modules pour lesquels on n'aurait pas forcément cassé sa tirelire, mais également pour la petite minorité qui fait tourner DCS sous Linux de vérifier si le module fonctionne avant de l'acheter.

J'avais donc l’œil fermement dirigé vers le JF-17, apparemment particulièrement bien modélisé, et je me suis empressé de le télécharger. Petite déception: autant l'avion fonctionne impeccable, autant j'ai chez moi des freezes systématiques dès que je veux quitter la mission, ce qui est un petit peu gonflant. Exit donc le JF-17.

Je zieutais également la carte Normandie, malgré des avis plutôt mitigés, histoire de donner un peu vie au P-51D. Là encore, déception : non seulement la carte (vendue 8 balles) ne suffit pas car il faut également le pack WW2 (hop, 25 balles supplémentaires), mais surtout, chez moi encore (ne généralisons pas!), impossible de charger la carte au sein du jeu, j'ai un freeze. Exit donc la carte Normandie, comme quoi j'ai bien fait d'essayer avant d'acheter.

Un autre module que je voulais essayer, c'est le FW-109, avion de la 2ème guerre mondiale, côté Allemand. Une fois passé la gêne de combattre pour les Nazis, le fait est que c'est un très bel avion, probablement bien plus aisé à prendre en main que le Mustang, ce qui ne m'empêche pas par ailleurs de me faire descendre en moins de 2. Belle bête donc, mais Nazi + 40 balles = grosse hésitation quand j'ai déjà le Mustang pour jouer. Ceci dit, pour quelqu'un qui n'aurait pas encore de warbird mais qui voudrait en faire, très bonne option.

Avec également des problèmes de chargement au niveau de l'A-10 et du F-16, c'est finalement assez décevant ! J'ai l'impression d'avoir eu beaucoup de chance que les modules que j'ai achetés sans avoir testé avant, soit le M-2000, le F-18 et le P-51D, fonctionnent très bien.

vendredi 17 avril 2020

Animation d'un tank dans Blender

Voici un sujet qui a fait couler beaucoup d'encre virtuelle ! Si animer une chenille dans une seule direction est quelque chose de relativement aisé, cela devient incroyablement plus complexe lorsqu'il s'agit de permettre le déplacement dans n'importe quelle direction, avec en plus la gestion du différentiel lors des rotations. Ce tutoriel reprend les techniques de base d'animation des chenilles, alors que celui là propose carrément de simuler une chenille en utilisant le moteur physique !

C'est finalement une approche de suivi de chemin décrite dans le premier tutoriel que j'ai sélectionnée. Ça marche plutôt pas mal, même si cela ne gère pas le différentiel entre les chenilles gauche et droite. Ceci dit, je suppose qu'il doit être possible d'ajuster le driver pour prendre en compte cette rotation. Ça reste un fudge, mais cela devrait être amplement suffisant pour ce que je vais en faire.