Pong ejecutándose en 240 pestañas del navegador
(eieio.games)- Se implementó un experimento de Pong que aprovecha el espacio vacío de la barra de pestañas no cerradas como si fuera una pantalla, usando los favicons de 240 pestañas de Chrome como píxeles
- La grilla de pestañas de 8×30 se creó con AppleScript, y cada pestaña calcula su posición mediante
windowIndexytabIndexen la consulta de la URL - El
setIntervalde las pestañas en segundo plano se ralentizaba a intervalos de alrededor de 1 segundo, pero al mover el temporizador a un Web Worker, la actualización del favicon se volvió lo bastante rápida - En lugar de WebSocket con servidor, se usó la Broadcast Channel API para sincronizar pestañas del mismo dominio, y la pestaña principal inicia la animación después de que todas las pestañas terminan de registrarse
- El mayor cuello de botella era volver a crear cientos de favicons en cada frame; al actualizar solo las pestañas que realmente cambiaban, la animación se volvió mucho más fluida
Cómo usar 240 pestañas como una sola pantalla
- El proyecto es un experimento para ejecutar Pong aprovechando el espacio que ocupan las pestañas abiertas del navegador
- La pantalla está formada por una grilla de pestañas de 8×30, con 8 ventanas de Chrome y 30 pestañas por ventana
- La pelota y las paletas se mueven de forma continua entre el canvas HTML de la ventana frontal y el área de favicons de la barra de pestañas superior
- El código está publicado en GitHub como faviconic, con la advertencia de que es código “awful” en estado de prototipo
La idea que nació de Flappy Favi
- La inspiración vino de Flappy Favi, una versión de Flappy Bird creada por Tru que se ejecuta dentro de un favicon
- Un favicon es una imagen pequeña que se muestra en la barra de pestañas del navegador, pero uno solo es demasiado pequeño y difícil de ver
- Para dibujar una imagen a través de varias pestañas, primero había que resolver tres problemas
- Cómo acomodar las pestañas en una grilla visualmente ordenada
- Cómo actualizar rápidamente los favicons incluso en pestañas en segundo plano
- Cómo sincronizar las pantallas de varias pestañas entre sí
Crear una grilla de pestañas de Chrome con AppleScript
- Al principio se comprobó la posibilidad repitiendo clics en el botón de nueva pestaña de Chrome para hacer las pestañas más pequeñas, y luego acomodando una segunda ventana para formar la segunda fila
- Crear una grilla grande manualmente era engorroso, así que se automatizó con AppleScript
- El script abre 8 ventanas de Chrome, crea 30 pestañas en cada una y posiciona las ventanas para que queden apiladas unas sobre otras
- Como Chrome intentaba volver a abrir pestañas cerradas, hubo que limpiarlas al inicio del script
- Cada URL de pestaña incluye
windowIndexytabIndex- A la última pestaña abierta se le agregan parámetros adicionales como
isMain=true,numWindows,numTabsyfullWidth
- A la última pestaña abierta se le agregan parámetros adicionales como
Actualizar rápidamente el favicon en pestañas en segundo plano
- El navegador busca una URL de favicon predeterminada, pero si se agrega en el
headde HTML un elemento que indica la ubicación del favicon y se actualiza, es posible cambiar el ícono - La actualización de favicons en Chrome se percibe en torno a unas 4 veces por segundo
- En pestañas en segundo plano se restringe el uso de recursos, por lo que incluso un pequeño loop que cambia el favicon cada 250 ms solo se ejecutaba aproximadamente una vez por segundo
- Se intentó usar como atajo los callbacks de audio en segundo plano de la Web Audio API, pero no se logró hacerlo funcionar correctamente
- Luego se movió el temporizador a un Web Worker, y el problema se resolvió haciendo que el worker enviara mensajes al documento principal para actualizar el favicon
- La estructura de ejemplo consiste en que el worker dibuja un emoji en un
OffscreenCanvas, lo convierte en undata URLy lo envía al documento principal- El documento principal cambia el
hrefdelink rel="shortcut icon"para actualizar realmente el favicon
- El documento principal cambia el
Registro y sincronización de pestañas con Broadcast Channel
- La comunicación entre pestañas tenía dos desafíos
- Cada pestaña debía saber en qué ventana y en qué número de pestaña estaba
- Había que decidir por qué canal de comunicación enviar las actualizaciones de pestañas
- La identificación de la posición se resolvió leyendo
windowIndexytabIndexdesde los parámetros de consulta de la URL insertados por AppleScript - La prueba de concepto inicial usaba una estructura en la que cada pestaña se conectaba a un servidor WebSocket y el servidor enviaba imágenes
- El enfoque con WebSocket no encajaba bien con el objetivo del proyecto
- Chocaba con la idea estética de querer desplegarlo en pestañas de navegadores de todo el mundo sin servidor
- Como cada pestaña se conectaba en momentos distintos, las actualizaciones no quedaban sincronizadas
- Luego se cambió a la Broadcast Channel API, que transmite información entre varias pestañas del mismo dominio
- Las pestañas en segundo plano envían por el broadcast channel un mensaje
registrationcon sus índices de pestaña/ventana, y la pestaña principal lo recibe y devuelve unack - La pestaña principal inicia la animación después de recibir el registro de todas las pestañas en segundo plano
Alinear el canvas con los favicons
- Para que los objetos dibujados en la ventana frontal parezcan “entrar” en la barra de pestañas, el canvas real y los favicons superiores se tratan como un único canvas extendido
- La implementación requirió medir directamente las dimensiones reales de la ventana de Chrome
- 92 píxeles desde el borde izquierdo de la ventana de Chrome hasta el primer favicon
- 58 píxeles desde la parte inferior del favicon hasta la parte superior real de la ventana
- El tamaño del favicon es de 16×16
- Con esas medidas y la cantidad de pestañas/ventanas se calculan tres valores
- El ancho del canvas que se mostrará para alinearse con los favicons
- El ancho de cada pestaña
- El ancho y alto del “canvas” completo, incluyendo la fila de favicons, la barra de URL y el canvas real
- Si un rectángulo está debajo de la barra de URL, se dibuja en el canvas real; si está por encima de la barra de URL, se transmite por broadcast la información de esa región a otras pestañas
- Cada pestaña calcula sus propias coordenadas de píxel y actualiza el favicon como píxel blanco/negro
El cuello de botella no era la transmisión de datos, sino la creación de favicons
- La implementación inicial funcionaba, pero la animación del canvas daba tirones y no se veía fluida aunque se dibujara dentro de
requestAnimationFrame - Al principio se sospechó que la estructura copiaba demasiados datos: el main thread calculaba el estado de todos los píxeles de favicon, lo metía en el broadcast channel y cientos de pestañas lo leían
- Se cambió para transmitir por broadcast solo la posición del rectángulo y que cada pestaña calculara por su cuenta si había intersección, pero el rendimiento no mejoró
- El problema real era que cada pestaña volvía a crear en cada frame una imagen de favicon blanco y negro de 4×4 y generaba una URL con
toDataURL("image/png") - Aunque el canvas resultante no cambiara, cientos de pestañas seguían regenerando varias veces por segundo un pequeño favicon blanco
- Al modificarlo para actualizar el favicon solo cuando realmente cambiaba, el rendimiento mejoró mucho
- Aun así, quedó sin entenderse con claridad por qué el trabajo de otras pestañas hacía tan lenta la animación de la pestaña frontal
Por qué eligió Pong en lugar de Snake
- Después del experimento de mover rectángulos, se ordenó el pequeño “motor” y se eligió qué juego construir encima
- La primera candidata fue Snake, porque al estar basado en bloques parecía encajar bien con los favicons
- Pero se consideró que, precisamente por estar tan basado en bloques, Snake no resaltaba tanto el efecto de convertir el movimiento continuo del canvas frontal en píxeles discretos en la barra de pestañas
- Finalmente se eligió Pong
- La pelota y las paletas tienen que ir y venir con frecuencia entre el canvas y la barra de pestañas
- Ese movimiento encaja bien con el efecto visual del proyecto
Notas de implementación de Pong
- Una vez lista la API para dibujar favicons, implementar Pong en sí fue relativamente simple
- El jugador de computadora de la derecha siempre intenta alinear el centro de su paleta con el centro de la pelota
- El ángulo cuando la pelota rebota en una paleta se calcula con trigonometría simple usando la posición relativa respecto del centro de la paleta
- Este método no es realista, pero evita que la pelota mantenga siempre el mismo ángulo
- Para la detección de colisiones se usa una función que verifica si dos rectángulos se intersectan
- Para enfatizar el efecto de que la pelota y las paletas entran suavemente en los favicons, se agregó un trail a la pelota
El cuarto juego creado en Recurse Center
- El proyecto se escribió durante un batch en Recurse Center
- Recurse Center se presenta como una especie de writer’s retreat gratuito para programar
- Este es el 4.º juego creado en unos 40 días
- Se invita a los lectores interesados a considerar postularse a Recurse Center
1 comentarios
Opiniones de Hacker News
¡Hola! Yo hice esto. Tenía curiosidad por saber si iba a pegar entre los lectores de HN :)
Si tienen preguntas, las respondo. Anotando algunas ideas sueltas: tengo mucha curiosidad por ver cómo se vería usando favicons animados. Firefox soporta favicons animados, así que, por ejemplo, si se predice la posición futura de la pelota y se genera un SVG animado, parece que se podría lograr una tasa de cuadros mucho mejor.
Un amigo me comentó offline que la rasterización de canvas normalmente la maneja la GPU, así que parece que mi intuición de rendimiento al ver la animación entrecortada estaba bastante equivocada.
Estoy solo relativamente seguro de que Chrome limita las actualizaciones del favicon a 4 por segundo. Hay varias formas de actualizar un favicon, así que quizá se me esté escapando algo.
[0] https://jakesgordon.com/writing/javascript-pong/part1/
Después de 1 Million Checkboxes, haber hecho esto, global cap locks, la regex de bad apple, uuid y demás muestra un impulso impresionante.
Vi la presentación reciente de Nolen en Recurse, y estos juegos de una sola vez completamente dementes, pero en el fondo graciosos y geniales, son realmente disfrutables.
Me recuerdan al internet de antes. A la época en que la gente hacía cosas simplemente para ser silly y divertirse. Anoche, inspirado por las cosas que publicó, hice un cuasi quine muy gracioso en el que una página imprime su propio código fuente usando descompilación de BEAM y algunos trucos.
Ojalá yo también tuviera tiempo para sacar cosas así una tras otra, y solo saber que hay gente que sigue haciendo cosas interesantes de esta manera me saca una sonrisa.
Para quien quiera reírse, la parte de ese sort-of quine está aquí: https://github.com/notactuallytreyanastasio/blog/blob/main/l...
Todo lo que hace Nolen me gusta. Para mí parece dar exactamente en el sweet spot de las apps/sitios de propósito único que me provocan nostalgia por cómo era el internet de antes.
Matthew Rayfield tiene un experimento parecido que usa la barra de direcciones en lugar de los favicons de las pestañas: https://www.youtube.com/watch?v=q7GtCLwTmV4
Al hacer esto sin duda tenía en mente su trabajo con URLs, pero ya había pasado bastante desde la última vez que leí su blog o vi sus videos. Fue muy divertido volver a mirarlo a mitad del proyecto y recordar que él también había ido por el lado de “apilar ventanas para crear 2D”.
Esto me recuerda a:
“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
Afirmo categóricamente que lo siguiente es Doom.
Además, en cada cuadro probablemente cambie una parte mucho mayor de la pantalla, así que para actualizar los favicons se necesitaría mucho más de “convertir canvas a URL de datos”. En la implementación actual eso es caro. Eso sí, no conocía OffscreenCanvas, y quizá eso ayude.
Si el problema es que las pestañas se vuelven más angostas en Firefox, todavía se puede inspeccionar el propio navegador Firefox, encontrar la regla de estilo de ancho mínimo de pestaña y ponerla en userChrome.css.
Hay varias guías relacionadas, y seguramente alguien ya lo dejó documentado para el ancho de las pestañas.
Aunque quizá eso sea hacer trampa. También existe
browser.tabs.tabMinWidth, pero si mal no recuerdo igual fuerza un mínimo.La banda Ok Go hizo hace tiempo un video musical en colaboración con Google Chrome, donde las ventanas del navegador y los bailarines se sincronizaban de forma increíble y también aparecían efectos tipo caleidoscopio. Esto me hizo acordar a eso.
https://www.youtube.com/watch?v=ISL1GfXwr-o