Nos sentamos a charlar con Ben, uno de nuestros programadores de la interfaz del usuario que ha corregido el error del “texto mezclado” que venía afectando a Path of Exile desde hacía tiempo. Como el error ha despertado interés, Ben amablemente ha escrito sobre el tema ahora que este ya no existe. ¡Echa un vistazo a continuación!



Después de anunciar que finalmente encontramos una corrección para el infame error del “texto desordenado” que los jugadores experimentan desde hace 6 años, algunos jugadores expresaron su interés por un “postmortem” de este antiguo error. Siempre me gustó la idea de probar mis habilidades para escribir alguna publicación sobre detalles técnicos, ¡y aquí está! El error ha recibido varios nombres diferentes, como texto “desordenado”, “corrupto” o “krangled” (un término acuñado por reddit para referirse a cosas corruptas). Internamente, nos referíamos a él sobre todo como texto “mezclado”, así que así lo llamaré aquí.

Puedo decirles que el error apareció en el código el 25 de abril de 2016 y llegó a producción en la 2.3.0 con la liga Prophecy. Se generó por la “refactorización” del motor de texto con el objetivo de implementar la compatibilidad con la versión de Xbox de Path of Exile que estaba por salir.

Los síntomas

Estoy convencido de que la mayoría de los jugadores se encontró con este error en algún momento durante todos estos años, generalmente después de jugar muchas horas, pero algunos jugadores encontraban el error con mucha más frecuencia. El error podía afectar a cualquier texto del juego y tenía dos efectos característicos: Uno donde el espacio entre los caracteres individuales era demasiado grande o demasiado pequeño.



Y otro donde los caracteres individuales estaban representados, gráficamente, por un símbolo incorrecto:


Algunos jugadores muy observadores notaron que, en el segundo caso, aplicar un código de sustitución podía restablecer el texto original.

Ejemplo: "Pevpf`^l D^j^db" -> "Physical Damage", ^ en realidad era "a".

Algo que siempre me resultó extraño era que las letras mayúsculas tenían equivalencias distintas (o ninguna) respecto de las letras minúsculas.

La cacería

El primer ticket de este error se creó el 4 de junio de 2016 a partir de reportes publicados en el foro poco después del comienzo de la liga Prophecy. El mayor inconveniente que encontramos fue que nunca pudimos reproducir el error en nuestras máquinas, sino que aparecía aleatoriamente muy de vez en cuando. Por lo que oí, solo logramos que apareciera en la PC de alguno de los programadores una o dos veces, y esto es crucial para que podamos investigar la memoria y encontrar pistas de cómo se origina. Hasta que pudiéramos encontrar los pasos para reproducirlo, lo mejor que podíamos hacer era implementar mejoras especulativas y esperar a que el problema dejara de ser reportado. Como no podíamos encontrar nada y no era un problema que impidiera la ejecución del juego, lo degradamos a un problema de importancia baja para poder dedicar más tiempo a características nuevas y otras correcciones.

Muchos desarrolladores (yo incluido) habían intentado encontrar el problema individualmente durante los años, mientras seguían apareciendo reportes de usuarios casi todos los meses para recordarnos este intrigante problema. Gracias a los reportes, las capturas de pantalla y mi propia experiencia, llegué a las siguientes conclusiones:

  • Afectaba a los estilos de fuente individuales (combinación de tipografía, tamaño y estado, como negrita o cursiva), en lugar de a los textos o a cadenas de caracteres (strings).
  • No parecía tener que ver con la generación de texturas, la corrupción ni el atlas de texturas, porque ninguno de los símbolos parecía verse cortado ni aparecía por la mitad. Las pocas veces que conseguimos reproducir el error en la máquina de un programador también se confirmó esto.
  • Salir del juego no resolvía el problema en la mayoría de los casos, sino que había que reiniciar el cliente.
  • Noté que nunca apareció un reporte del tema en Xbox, PlayStation ni MacOS, lo que ayudó a acotar el problema a un área en particular del motor de texto.

Cerca del lanzamiento de Scourge, noté que parecía que había reportes más frecuentes del error y empecé a encontrar más seguido el error mientras jugaba. Grabé la mayoría de estas ocurrencias, reuní imágenes de los jugadores y comencé a construir algunas ideas posibles, pero seguía sin poder encontrar los pasos para reproducir el error más allá de “jugar mucho”. Hace algunas semanas, tuve algo de descanso entre mis tareas y decidí hacer un nuevo intento. Para ello, pasé varios días inmerso en el motor de texto para leer y entender todas sus complejidades.

La corrección

Mientras revisaba profundamente el código del motor de texto, terminé encontrando la siguiente función:

SCRIPT_CACHE* ShapingEngineUniscribe::GetFontScriptCache( const Resources::Font& font )
{
    const auto font_resource = font.GetResource()->GetPointer();
    // `font_script_caches` here is a map of `const FontResource*` to `SCRIPT_CACHE` values
    auto it = font_script_caches.find( font_resource );
    if( it == std::end( font_script_caches ) )
        it = font_script_caches.emplace( std::make_pair( font_resource, nullptr ) ).first;
    return &it->second;
}

Para quienes no son programadores: esta función hace referencia a un recurso particular de las fuentes y usa su ubicación en la memoria como llave (valor de búsqueda) para un objeto SCRIPT_CACHE y crea una entrada nueva si aún no existe una. Luego, la función le devuelve un puntero al objeto SCRIPT_CACHE, que permite que la función que lo invoca modifique el SCRIPT_CACHE almacenado en lugar de crear una copia que no conservaría sus cambios en el mapa “font_script_caches”.
El objeto SCRIPT_CACHE de aquí, es un objeto opaco de datos que usa la biblioteca de Windows Uniscribe (que solo usamos para la versión de Windows del juego). La documentación de Uniscribe no indica qué información está almacenando exactamente, solo aclara que la aplicación necesita guardar un objeto de cada tipo por cada “estilo de caracteres” que se use. Debido a los efectos del error de texto mezclado, podemos inferir que se usa para vincular los caracteres y su espaciado a las texturas de los símbolos.

A primera vista, esta función parece estar haciendo algo completamente razonable, y es probable que por esto no hayamos detectado el problema durante todos estos años. Solo ves el problema una vez que te has dado cuenta de que los recursos de las fuentes pueden ser descargados por nuestro administrador de recursos cuando ya no se usan más. El error, entonces, se generaba cuando otra fuente (con diferente tipografía, estilo o tamaño) era cargada por el administrador de recursos en la misma ubicación en la memoria, lo que causaba que la fuente nueva volviera a usar el SCRIPT_CACHE viejo.
Después de encontrar esto, hice varias pruebas para confirmar mi teoría de que este era el problema.

Al forzar que todas las fuentes usaran el mismo caché, el problema apareció inmediatamente al iniciar el juego:


¡Tadá! Que encima aparecieran ambos síntomas a la vez, confirmaba que los efectos los causaba el mismo problema y no dos separados. A partir de aquí, pude reproducir el error naturalmente al cargar y descargar a propósito la mayor cantidad de fuentes posibles hasta obtener una fuente nueva que ocupara la ubicación de memoria de una fuente vieja.


Ahora que conocíamos el problema, había varias maneras de resolverlo: podíamos mover el objeto SCRIPT_CACHE para que perteneciera al objeto Resource::Font, eliminar el SCRIPT_CACHE viejo cada vez que la fuente se descargara o intercambiar el valor de búsqueda de la dirección de la memoria para que, en lugar de eso, se basara en la tipografía, el tamaño y el estilo de la fuente, que es lo que realmente hace que una fuente sea única. Todas estas opciones funcionan pero cada una tiene sus pros y sus contras y debería sopesarse según como encaje en los sistemas más grandes.

Resumen

La causa real del error no es lo verdaderamente interesante, lo más interesante es darse cuenta de que las direcciones de memoria pueden ser reutilizadas y se reutilizan, así que hay que ser cuidadoso al usar los punteros como llaves. Este error permanecerá en mi memoria por sus síntomas tan extraños, por lo molesto que ha sido localizarlo e incluso por la notoriedad que ha tenido al durar tanto tiempo. Puede que hasta lo eche de menos ya que es un “gran misterio” menos al que darle vueltas en mi cerebro. Supongo que simplemente tendré que buscar el siguiente problema misterioso que resolver.

¡Gracias a todos los que reportaron este error y otros problemas a lo largo de los años! En el desarrollo de software, la depuración de errores puede ser extraña y hasta las cosas más diminutas pueden provocar errores muy raros. Los reportes detallados de errores son muy valiosos para que podamos crear una imagen de lo que puede estar ocurriendo y nos ayudan a reproducir el problema, lo cual nos sirve para desarrollar y probar correcciones de errores en lugar de dar palos a ciegas.


R.I.P T l e e v
Posted by 
on
Grinding Gear Games
Lo vi crecer desde pequeño... Te vamos a extrañar =D
Problemas Conocidos / Preguntas Frecuentes - ¡Por favor lee antes de publicar!

Cómo publicar una imagen

Guía de soporte técnico

MTR y el Manual de Problemas de Conectividad

"Una pulga no puede picar a una locomotora, pero puede llenar de ronchas al maquinista." (Libertad)

IGN: @Copied_to_clipboard

Report Forum Post

Report Account:

Report Type

Additional Info