Pong exécuté dans 240 onglets de navigateur
(eieio.games)- Une expérimentation a permis d’exécuter Pong en utilisant l’espace vide de la barre d’onglets non fermés comme un écran, avec les favicons de 240 onglets Chrome servant de pixels
- La grille de 8×30 onglets est créée avec AppleScript, et chaque onglet calcule sa position à partir de
windowIndexettabIndexdans les paramètres de requête de l’URL - Le
setIntervaldes onglets en arrière-plan ralentissait jusqu’à environ une seconde d’intervalle, mais en déplaçant le timer dans un Web Worker, les mises à jour des favicons sont devenues suffisamment rapides - Au lieu d’un serveur WebSocket, le projet utilise la Broadcast Channel API pour synchroniser les onglets d’un même domaine, et l’onglet principal ne démarre l’animation qu’une fois tous les onglets enregistrés
- Le principal goulot d’étranglement était la recréation de centaines de favicons à chaque frame ; en ne mettant à jour que les onglets réellement modifiés, l’animation est devenue nettement plus fluide
Utiliser 240 onglets comme un seul écran
- Le projet est une expérimentation qui exécute Pong en exploitant l’espace occupé par des onglets de navigateur non fermés
- L’écran est constitué d’une grille de 8×30 onglets, formée de 8 fenêtres Chrome avec 30 onglets par fenêtre
- La balle et les raquettes se déplacent entre le canvas HTML de la fenêtre au premier plan et la zone de favicon de la barre d’onglets située au-dessus
- Le code est publié sur GitHub dans faviconic, avec la mention qu’il s’agit d’un prototype au code “awful”
Une idée née de Flappy Favi
- L’inspiration vient de Flappy Favi, une version de Flappy Bird créée par Tru qui tourne dans un favicon
- Un favicon est une petite image affichée dans la barre d’onglets du navigateur, mais un seul favicon est trop petit pour être vraiment lisible
- Pour dessiner une image à travers plusieurs onglets, il fallait d’abord résoudre trois problèmes
- comment disposer les onglets en grille de façon agréable
- comment mettre à jour rapidement les favicons, même dans les onglets en arrière-plan
- comment synchroniser l’affichage entre plusieurs onglets
Créer une grille d’onglets Chrome avec AppleScript
- Au départ, la faisabilité a été vérifiée en cliquant plusieurs fois sur le bouton de nouvel onglet de Chrome pour réduire la taille des onglets, puis en alignant une deuxième fenêtre pour former une seconde rangée
- Comme créer une grande grille à la main était fastidieux, l’opération a été automatisée avec AppleScript
- Le script ouvre 8 fenêtres Chrome, crée 30 onglets dans chacune, puis place les fenêtres de manière à les empiler les unes au-dessus des autres
- Chrome essayait de rouvrir des onglets précédemment fermés, ce qui a nécessité un nettoyage au démarrage du script
- L’URL de chaque onglet contient
windowIndexettabIndex- Le dernier onglet ouvert reçoit aussi des paramètres supplémentaires comme
isMain=true,numWindows,numTabsetfullWidth
- Le dernier onglet ouvert reçoit aussi des paramètres supplémentaires comme
Mettre à jour rapidement les favicons en arrière-plan
- Le navigateur cherche un favicon par défaut, mais on peut changer l’icône en ajoutant dans le
headHTML un élément indiquant son emplacement, puis en le mettant à jour - Dans Chrome, le rafraîchissement des favicons semble plafonner à environ 4 fois par seconde
- Dans les onglets en arrière-plan, l’usage des ressources est limité, si bien qu’une petite boucle censée changer le favicon toutes les 250 ms ne s’exécutait en pratique qu’environ une fois par seconde
- Une tentative d’utiliser les callbacks audio en arrière-plan de la Web Audio API comme contournement n’a pas abouti
- Le problème a finalement été résolu en déplaçant le timer dans un Web Worker, qui envoie ensuite des messages au document principal pour mettre à jour le favicon
- Dans l’exemple, le worker dessine un emoji sur un
OffscreenCanvas, le convertit endata URL, puis l’envoie au document principal- Le document principal met alors à jour le
hrefdelink rel="shortcut icon"pour changer le favicon réel
- Le document principal met alors à jour le
Enregistrement et synchronisation des onglets avec Broadcast Channel
- La communication entre onglets posait deux problèmes
- chaque onglet devait savoir dans quelle fenêtre et à quelle position il se trouvait
- il fallait choisir quel canal de communication utiliser pour transmettre les mises à jour
- L’identification de la position est résolue en lisant
windowIndexettabIndexdans les paramètres de requête d’URL ajoutés par AppleScript - Le proof of concept initial reposait sur une connexion de chaque onglet à un serveur WebSocket qui envoyait ensuite les images
- L’approche WebSocket ne correspondait pas vraiment aux objectifs du projet
- elle allait à l’encontre du souhait, pour des raisons esthétiques, de pouvoir déployer cela dans des onglets de navigateur partout dans le monde sans serveur
- les onglets se connectant à des moments différents, les mises à jour n’étaient pas synchronisées
- Le projet est ensuite passé à la Broadcast Channel API, qui permet d’échanger des informations entre plusieurs onglets d’un même domaine
- Les onglets en arrière-plan envoient sur le broadcast channel un message
registrationcontenant leurs indices d’onglet et de fenêtre, puis l’onglet principal répond avec unack - L’onglet principal ne lance l’animation qu’après avoir reçu l’enregistrement de tous les onglets en arrière-plan
Aligner le canvas et les favicons
- Pour donner l’impression que les objets dessinés dans la fenêtre au premier plan “entrent” dans la barre d’onglets, le canvas réel et les favicons situés au-dessus sont traités comme un seul canvas étendu
- La mise en œuvre a nécessité de mesurer directement les dimensions réelles de la fenêtre Chrome
- 92 pixels entre le bord gauche de la fenêtre Chrome et le premier favicon
- 58 pixels entre le bas du favicon et le haut de la fenêtre réelle
- une taille de favicon de 16×16
- À partir de ces mesures et du nombre de fenêtres/onglets, trois valeurs sont calculées
- la largeur du canvas à afficher pour qu’il soit aligné avec les favicons
- la largeur de chaque onglet
- la largeur et la hauteur du “canvas” total, incluant la rangée de favicons, la barre d’URL et le canvas réel
- Si un rectangle se trouve sous la barre d’URL, il est dessiné dans le canvas réel ; s’il se trouve au-dessus de la barre d’URL, les informations sur cette zone sont diffusées aux autres onglets
- Chaque onglet calcule alors ses propres coordonnées en pixels, puis met à jour son favicon en pixels noirs et blancs
Le goulot d’étranglement n’était pas le transfert de données, mais la génération des favicons
- L’implémentation initiale fonctionnait, mais l’animation du canvas saccadait, et même en dessinant dans
requestAnimationFrame, elle n’était pas fluide - Au début, on soupçonnait le thread principal de calculer l’état de tous les pixels des favicons, puis de le placer dans le broadcast channel, ce qui imposait trop de copies de données à des centaines d’onglets
- Le système a été modifié pour ne diffuser que la position des rectangles, en laissant chaque onglet calculer lui-même l’intersection, mais les performances ne se sont pas améliorées
- Le vrai problème venait du fait que chaque onglet recréait à chaque frame une image de favicon noir et blanc en 4×4, puis générait une URL avec
toDataURL("image/png") - Même quand le canvas résultant ne changeait pas, des centaines d’onglets continuaient ainsi à régénérer plusieurs fois par seconde de petits favicons blancs
- Une fois les mises à jour limitées aux seuls cas où le favicon change réellement, les performances se sont nettement améliorées
- En revanche, il restait difficile d’expliquer clairement pourquoi le travail effectué dans les autres onglets ralentissait autant l’animation de l’onglet au premier plan
Pourquoi Pong plutôt que Snake
- Après les expérimentations sur le déplacement de rectangles, un petit “moteur” a été mis au propre, puis le choix du jeu à construire dessus s’est posé
- Le premier candidat était Snake, qui semblait bien adapté aux favicons grâce à sa logique en blocs
- Mais Snake était jugé trop basé sur des blocs, ce qui atténuait l’effet visuel de transformation du mouvement continu du canvas principal en pixels discrets dans la barre d’onglets
- Le choix final s’est porté sur Pong
- la balle et les raquettes devaient traverser fréquemment le canvas et la barre d’onglets
- ce mouvement correspondait très bien à l’effet visuel recherché par le projet
Notes d’implémentation de Pong
- Une fois l’API de dessin des favicons prête, l’implémentation de Pong elle-même a été relativement simple
- Le joueur ordinateur à droite essaie en permanence d’aligner le centre de sa raquette avec le centre de la balle
- L’angle de rebond de la balle sur une raquette est calculé avec une trigonométrie simple, à partir de la position relative par rapport au centre de la raquette
- Cette méthode n’est pas réaliste, mais elle évite que la balle conserve toujours le même angle
- La détection des collisions utilise une fonction qui vérifie si deux rectangles se croisent
- Pour accentuer l’effet d’entrée fluide de la balle et des raquettes dans les favicons, une traînée a été ajoutée à la balle
Le quatrième jeu créé au Recurse Center
- Le projet a été réalisé pendant un batch du Recurse Center
- Le Recurse Center est présenté comme une sorte de writer’s retreat gratuit pour la programmation
- Ce projet était le quatrième jeu créé en environ 40 jours
- Les lecteurs intéressés sont invités à envisager de postuler au Recurse Center
1 commentaires
Commentaires sur Hacker News
Bonjour ! C’est moi qui ai fait ça. J’étais curieux de voir si ça parlerait aux lecteurs de HN :)
Si vous avez des questions, j’y répondrai. Pour poser quelques idées en vrac, je suis vraiment curieux de voir ce que ça donnerait avec des favicons animés. Firefox prend en charge les favicons animés, donc par exemple, en prédisant la position future de la balle et en générant un SVG animé, on pourrait sans doute obtenir une bien meilleure fréquence d’images.
Un ami m’a fait remarquer hors ligne que la rastérisation du canvas est généralement gérée par le GPU, donc mon intuition sur les performances en voyant l’animation saccadée était probablement assez fausse.
Je ne suis que moyennement sûr que Chrome limite les mises à jour de favicon à 4 fois par seconde. Il existe plusieurs façons de rafraîchir une favicon, donc j’ai peut-être raté quelque chose.
[0] https://jakesgordon.com/writing/javascript-pong/part1/
Après 1 Million Checkboxes, avoir créé ça, global cap locks, la regex Bad Apple, uuid, etc., c’est une dynamique impressionnante.
J’ai vu la récente présentation de Nolen à Recurse, et ces jeux ponctuels complètement fous mais fondamentalement drôles et brillants sont un vrai plaisir.
Ça me rappelle l’ancien Internet. L’époque où les gens construisaient des trucs juste pour faire quelque chose de silly et s’amuser. Hier soir, inspiré par ce qu’il a publié, j’ai bricolé une quasi-quine très drôle où la page affiche son propre code source, avec de la décompilation BEAM et quelques astuces.
J’aimerais avoir le temps d’en produire à la chaîne moi aussi, et le simple fait qu’il y ait des gens qui continuent à créer des choses intéressantes de cette façon me fait sourire.
Pour ceux qui veulent rire, la partie sort-of quine est ici : https://github.com/notactuallytreyanastasio/blog/blob/main/l...
Tout ce que fait Nolen est génial. Pour moi, il touche exactement le sweet spot des apps/sites à usage unique qui me donnent de la nostalgie de ce qu’était l’ancien Internet.
Matthew Rayfield a aussi mené une expérience similaire en utilisant la barre d’adresse au lieu des favicons des onglets : https://www.youtube.com/watch?v=q7GtCLwTmV4
J’avais clairement son travail autour des URL en tête en faisant ça, mais cela faisait un bon moment que je n’avais pas lu son blog ni vu ses vidéos. En le revoyant au milieu du projet, c’était vraiment amusant de me rappeler qu’il était lui aussi allé dans la direction « empiler des fenêtres pour faire de la 2D ».
Ça me rappelle ceci :
“Show HN: I saw this mind-blowing experiment, so I made a simple version of it” (25/11/2023) https://news.ycombinator.com/item?id=38413660
“Synchronize a 3D scene across multiple windows using Three.js and localStorage” (27/11/2023) https://news.ycombinator.com/item?id=38437773
Je vous le garantis, le prochain, c’est Doom.
Et à chaque frame, une plus grande partie de l’écran risque de changer, donc pour rafraîchir les favicons, il faudrait beaucoup plus de conversions « canvas vers URL de données ». Dans l’implémentation actuelle, c’est coûteux. Cela dit, je ne connaissais pas OffscreenCanvas, et ça pourrait aider.
Si le problème est que les onglets deviennent trop étroits dans Firefox, on peut toujours inspecter le navigateur Firefox lui-même pour trouver la règle de style de largeur minimale des onglets, puis la mettre dans userChrome.css.
Il existe quelques guides à ce sujet, et quelqu’un a probablement déjà documenté ça pour la largeur des onglets.
Cela dit, c’est peut-être de la triche. Il y a aussi
browser.tabs.tabMinWidth, mais dans mon souvenir, une valeur minimale reste imposée.Le groupe Ok Go avait autrefois fait un clip en collaboration avec Google Chrome, où les fenêtres du navigateur et les danseurs étaient synchronisés de façon vraiment impressionnante, avec aussi un effet de kaléidoscope. Ça m’y a fait penser.
https://www.youtube.com/watch?v=ISL1GfXwr-o