Technical Article

Biblioteca de componentes Alcinoe y compatibilidad con Delphi 11.1 Alexandria

Alcinoe es una biblioteca de componentes de código abierto para Delphi y C++Builder, mantenida en GitHub por Zeus64. Cubre terreno que el VCL y el RTL de FireMonkey dejan a terceros: un reproductor de vídeo acelerado por GPU, un wrapper de WebRTC, controles de edición nativos para iOS y Android, un analizador JSON/BSON de doble modo, un cliente MongoDB con agrupación de conexiones, un wrapper de ImageMagick y una colección de controles FireMonkey que esquivan por completo la canalización de renderizado estándar. La biblioteca construyó su reputación sobre Rio (10.3.3) y Sydney (10.4.2), y desde entonces ha seguido cada versión de Embarcadero. En el momento de redactar este artículo, es totalmente compatible con Delphi 11.1 Alexandria y Delphi Athens 12.3.

Incorporar Alcinoe a un proyecto

La instalación se bifurca en una sola pregunta: ¿necesitáis soporte en tiempo de diseño para los controles visuales de Alcinoe? Si no es así, omitid el BPL por completo. Añadid {alcinoe_rootdir}\source a la ruta de búsqueda de bibliotecas del proyecto y listo. Todos los componentes no visuales, incluidos los analizadores, los clientes de base de datos y las utilidades de cadena, compilan desde el código fuente sin registrar nada.

Cuando sí necesitáis soporte en tiempo de diseño, el proceso es algo más largo. Abrid Component > Install Packages en el IDE de Delphi, buscad el BPL que corresponde a vuestra versión (por ejemplo {alcinoe_rootdir}\lib\bpl\alcinoe\Win32\alexandria\Alcinoe_alexandria.bpl), instaladlo y añadid igualmente {alcinoe_rootdir}\source a la ruta de búsqueda. El BPL registra los componentes; el directorio de fuentes es lo que el compilador encuentra al compilar vuestro proyecto.

Alcinoe incluye parches opcionales para las fuentes del RTL de Embarcadero. Si los queréis, navegad a {alcinoe_rootdir}\embarcadero\, elegid el subdirectorio correspondiente a vuestra versión y ejecutad update.bat. El script requiere GIT en el PATH y asume una ubicación de instalación predeterminada de Embarcadero. Descarga el código fuente original del RTL y aplica los parches. Una vez hecho, añadid ese directorio de fuentes parcheadas a la ruta de búsqueda del proyecto para que el compilador lo encuentre antes que la copia de solo lectura del árbol de instalación de Embarcadero. Nada de esto es necesario para empezar; solo importa si encontráis errores que los parches corrigen.

Android y el proxy de desugaring de D8

Varios componentes de Alcinoe (WebRTC, vídeo respaldado por ExoPlayer) dependen de bibliotecas Java que usan características del lenguaje Java 8. La cadena de herramientas Android que se incluye con las versiones más antiguas de Delphi usa dx.bat para la conversión DEX, que no puede manejar esos bytecodes en niveles de API inferiores a 26. La solución es el desugaring, que D8 gestiona automáticamente cuando se invoca directamente. Alcinoe proporciona un script proxy en {alcinoe_rootdir}\tools\D8Proxy\dx.bat que reenvía las llamadas del sistema de compilación de Delphi a D8, haciendo el desugaring transparente. Reemplazad el dx.bat original en el directorio de build-tools de vuestro SDK de Android (normalmente C:\SDKs\android\build-tools\30.0.3\) con este proxy. Embarcadero registró el problema subyacente en RSP-24155; versiones posteriores de las herramientas del SDK lo resolvieron directamente, así que comprobad si vuestra cadena de herramientas actual todavía necesita el workaround.

El problema de renderizado de FireMonkey y la solución de Alcinoe

El ciclo de pintado predeterminado de FireMonkey se convierte en un cuello de botella en interfaces con mucho desplazamiento. Un solo TRectangle con esquinas redondeadas puede tardar alrededor de 3 ms en repintarse porque la implementación estándar recalcula el trazado en cada fotograma. Con 20 controles de este tipo visibles, eso suma 60 ms por pasada de fotograma, lo que limita la tasa de fotogramas efectiva muy por debajo del umbral necesario para un desplazamiento fluido.

Alcinoe aborda esto con un búfer residente en GPU por control. El primer pintado renderiza el control en una TTexture almacenada en la memoria GPU. Los repintados posteriores transfieren esa textura en lugar de volver a ejecutar el algoritmo de pintado. El resultado medido en el mismo rectángulo redondeado cae de alrededor de 3 ms a alrededor de 0,1 ms. Más allá del búfer, Alcinoe sustituye el dibujo de trazados OpenGL para formas básicas por las APIs de dibujo nativas de Android e iOS, eludiendo el compromiso calidad/rendimiento vinculado a Form.Quality. Los controles relevantes son TALRectangle, TALCircle y un conjunto de contenedores de disposición mejorados, incluidos un ScrollBox y un TabControl.

TALJsonDocument: DOM y SAX en un solo tipo

TALJsonDocument es el analizador JSON y BSON de Alcinoe. Admite dos modos de recorrido. El modo DOM construye un árbol de objetos en memoria, lo que permite acceso aleatorio a cualquier nodo a costa de memoria proporcional al tamaño del documento. El modo SAX dispara eventos a medida que el analizador lee cada token sin retener ningún árbol, que es la opción correcta cuando necesitáis filtrar un documento grande y conservar solo unos pocos valores. Los analizadores DOM en Delphi (DBXJSON, SuperObject y los demás) son típicamente de tres a cinco veces más lentos que un enfoque SAX para el mismo contenido, porque cada asignación de nodo conlleva la sobrecarga de creación de objetos además del propio trabajo de análisis.

El tipo sigue el mismo patrón de navegación por nodos que TALXMLDocument. Una lectura DOM mínima tiene este aspecto:

MyJsonDoc.LoadFromJSON(AJsonStr, False {dom mode});
MyJsonDoc.ParseOptions := [poAllowComments];

// read scalar values
ShowMessage(MyJsonDoc.ChildNodes[‘name’].ChildNodes[‘first’].Text);
ShowMessage(IntToStr(MyJsonDoc.ChildNodes[‘_id’].Int32));

// iterate an array
for I := 0 to MyJsonDoc.ChildNodes[‘contribs’].ChildNodes.Count - 1 do
  Writeln(MyJsonDoc.ChildNodes[‘contribs’].ChildNodes[I].Text);

Para el modo SAX, asignad un procedimiento anónimo a OnParseText antes de llamar a LoadFromJSON con el segundo argumento establecido en True. La devolución de llamada recibe la ruta del nodo, el nombre, el valor y un TALJSONNodeSubType que identifica el tipo JSON (cadena, entero, flotante, booleano, etc.). Ese modo no produce asignaciones de heap para los nodos, por lo que escala a documentos de tamaño arbitrario sin agotar el presupuesto de memoria.

TALJsonDocument también lee y escribe BSON de forma nativa; pasad True como indicador BSON a LoadFromFile o SaveToFile. Una segunda variante, TALJsonDocumentU, usa UnicodeString (UTF-16) internamente en lugar de AnsiString (UTF-8) para contextos donde el código circundante trabaja en Unicode a lo largo de todo el proceso.

Cliente MongoDB y agrupación de conexiones

El controlador MongoDB de Alcinoe cubre las operaciones de consulta habituales y gestiona la agrupación de conexiones de forma nativa. El cliente simple, TAlMongoDBClient, abre y cierra una sola conexión por operación. La variante con pool, TAlMongoDBConnectionPoolClient, mantiene un conjunto de conexiones activas y entrega una a cada hilo llamante del pool, devolviéndola cuando la llamada se completa. Ese modelo evita que varios hilos se bloqueen mutuamente durante la configuración de la conexión, lo que importa cuando los workers en segundo plano consultan la misma base de datos simultáneamente. Para cursores de seguimiento en colecciones limitadas, TAlMongoDBTailMonitoringThread observa la llegada de nuevos documentos y dispara una devolución de llamada cuando llegan, que es el patrón estándar para streaming de registros o notificaciones de cambios sin sondeo.

Otros componentes de interés

ALVideoPlayer renderiza el vídeo en una TTexture en lugar de una ventana superpuesta, de modo que otros controles FireMonkey pueden situarse por encima en el orden Z. El backend de Android usa ExoPlayer, que añade compatibilidad con DASH, HLS y SmoothStreaming más allá de lo que gestiona el MediaPlayer integrado de Android. El backend de iOS usa AVPlayer con soporte equivalente de HLS.

TALWebRTC envuelve la pila WebRTC para audio y vídeo entre pares. No requiere navegador ni plugin, y la conexión atraviesa NAT mediante la negociación ICE/STUN/TURN estándar que gestiona la biblioteca subyacente.

TALStringList reemplaza el ordenamiento basado en AnsiCompareText de TStringList por una comparación ordinal independiente del locale y un quicksort hasta 10 veces más rápido en listas grandes. La variante con hash, TALHashedStringList, añade una tabla hash interna para búsqueda en O(1) a costa de una sobrecarga ligeramente mayor en listas pequeñas. Notad que TALStringList es una lista de AnsiString de 8 bits, no Unicode; encaja bien en código del lado del servidor donde UTF-8 es la codificación de trabajo y el rendimiento bruto importa más que la comparación sensible al locale.

En Windows de 64 bits, la herencia de FastCode que otorgó a muchas rutinas de cadena de Alcinoe su ventaja de velocidad (principalmente ensamblador x86 escrito a mano) no se traslada. Las compilaciones Win64 recurren a las implementaciones Pascal, que son notablemente más lentas en cargas de trabajo intensivas con cadenas. El proyecto demo\ALStringBenchMark permite medir la diferencia en vuestro hardware antes de comprometeros con una compilación de 64 bits donde el rendimiento de cadenas es un cuello de botella.

El código fuente completo está en github.com/Zeus64/alcinoe.