Technical Article

Biblioteca de Componentes Alcinoe e Compatibilidade com Delphi 11.1 Alexandria

O Alcinoe é uma biblioteca de componentes de código aberto para Delphi e C++Builder, mantida no GitHub por Zeus64. Abrange áreas que a VCL e a RTL do FireMonkey deixam para terceiros: um leitor de vídeo acelerado por GPU, um wrapper de WebRTC, controlos nativos de edição para iOS e Android, um analisador dual JSON/BSON, um cliente MongoDB com pooling de ligações, um wrapper de ImageMagick e uma coleção de controlos FireMonkey que evitam completamente o pipeline de renderização padrão. A biblioteca construiu a sua reputação nas versões Rio (10.3.3) e Sydney (10.4.2) e, desde então, tem acompanhado cada lançamento da Embarcadero. À data de redação deste artigo, é totalmente compatível com o Delphi 11.1 Alexandria e o Delphi Athens 12.3.

Integrar o Alcinoe num projeto

A instalação divide-se numa questão: necessita de suporte em tempo de desenho (design-time) para os controlos visuais do Alcinoe? Se não, ignore completamente a BPL. Adicione {alcinoe_rootdir}\source ao caminho de pesquisa da biblioteca (library search path) do projeto e está concluído. Todos os componentes não visuais, incluindo analisadores, clientes de base de dados e utilitários de strings, compilam a partir do código fonte sem necessidade de registar nada.

Quando necessita de suporte em tempo de desenho, o caminho é ligeiramente mais longo. Abra Component > Install Packages no IDE Delphi, procure a BPL correspondente à sua versão (por exemplo, {alcinoe_rootdir}\lib\bpl\alcinoe\Win32\alexandria\Alcinoe_alexandria.bpl), instale-a e adicione na mesma {alcinoe_rootdir}\source ao caminho de pesquisa. A BPL regista os componentes; o diretório de origem é o que o compilador encontra ao compilar o seu projeto.

O Alcinoe disponibiliza patches opcionais para os ficheiros de origem da RTL da Embarcadero. Se pretender utilizá-los, aceda a {alcinoe_rootdir}\embarcadero\, escolha o subdiretório correspondente à sua versão e execute update.bat. O script espera que o GIT esteja no PATH e assume a localização padrão de instalação da Embarcadero. Ele obtém o código fonte original da RTL e aplica as correções. Uma vez concluído, adicione esse diretório de código fonte corrigido ao caminho de pesquisa do seu projeto para que o compilador o selecione antes da cópia de leitura que se encontra na árvore de instalação da Embarcadero. Nada disto é obrigatório para começar; apenas importa se encontrar erros que os patches resolvam.

Android e o proxy de desugaring D8

Vários componentes do Alcinoe (WebRTC, vídeo baseado em ExoPlayer) dependem de bibliotecas Java que utilizam funcionalidades de linguagem do Java 8. A cadeia de ferramentas (toolchain) do Android que acompanha as versões mais antigas do Delphi utiliza o dx.bat para a conversão DEX, o qual não consegue processar esses bytecodes em níveis de API inferiores a 26. A solução é o desugaring, que o D8 gera automaticamente quando invocado diretamente. O Alcinoe fornece um script de proxy em {alcinoe_rootdir}\tools\D8Proxy\dx.bat que reencaminha as chamadas do sistema de compilação do Delphi para o D8, tornando o desugaring transparente. Substitua o dx.bat original no diretório build-tools do SDK Android (geralmente C:\SDKs\android\build-tools\30.0.3\) por este proxy. A Embarcadero registou o problema subjacente em RSP-24155; versões posteriores das ferramentas do SDK resolveram-no diretamente, pelo que deve verificar se a sua cadeia de ferramentas atual ainda necessita desta solução temporária.

O problema de renderização do FireMonkey e a resposta do Alcinoe

O ciclo de desenho padrão do FireMonkey torna-se um gargalo em interfaces com muito deslocamento (scroll). Um único TRectangle com cantos arredondados pode demorar cerca de 3 ms a redesenhar, porque a implementação padrão recalcula o caminho em cada frame. Com 20 destes controlos visíveis, isso soma 60 ms por passagem de frame, o que limita a taxa de frames efetiva muito abaixo do limiar para um deslocamento fluido.

O Alcinoe resolve este problema com um buffer residente na GPU por controlo. O primeiro desenho renderiza o controlo para uma TTexture armazenada na memória da GPU. Os redesenhos subsequentes desenham essa textura (blit) em vez de reexecutar o algoritmo de desenho. O resultado medido no mesmo retângulo arredondado cai de cerca de 3 ms para aproximadamente 0.1 ms. Além do buffering, o Alcinoe substitui o desenho de caminhos OpenGL para formas básicas por APIs de desenho nativas de Android e iOS, contornando a relação compromisso entre qualidade e desempenho associada a Form.Quality. Os controlos relevantes são TALRectangle, TALCircle e um conjunto de contentores de layout melhorados, incluindo um ScrollBox e TabControl.

TALJsonDocument: DOM e SAX num único tipo

TALJsonDocument é o analisador JSON e BSON do Alcinoe. Suporta dois modos de travessia. O modo DOM constrói uma árvore de objetos em memória, oferecendo acesso aleatório a qualquer nó à custa de memória proporcional ao tamanho do documento. O modo SAX aciona eventos à medida que o analisador lê cada token, sem reter qualquer árvore, sendo a escolha certa quando necessita de filtrar um documento grande e reter apenas alguns valores. Os analisadores DOM no Delphi (DBXJSON, SuperObject e outros) são tipicamente três a cinco vezes mais lentos do que uma abordagem SAX para o mesmo conteúdo, porque cada alocação de nó acarreta sobrecarga de criação de objetos além do próprio trabalho de análise.

O tipo segue o mesmo padrão de navegação de nós do TALXMLDocument. Uma leitura DOM minimal assemelha-se a isto:

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 o modo SAX, atribua um procedimento anónimo a OnParseText antes de chamar LoadFromJSON com o segundo argumento definido como True. O callback recebe o caminho do nó, nome, valor e um TALJSONNodeSubType que identifica o tipo JSON (string, integer, float, boolean, etc.). Esse modo não produz alocações na heap para os nós, pelo que se adapta a documentos de tamanho incalculável sem exceder o orçamento de memória.

TALJsonDocument também lê e escreve BSON nativamente; passe True como a flag BSON para LoadFromFile ou SaveToFile. Uma segunda variante, TALJsonDocumentU, utiliza UnicodeString (UTF-16) internamente em vez de AnsiString (UTF-8) para contextos em que o código circundante trabalha com Unicode.

Cliente MongoDB e pooling de ligações

O driver MongoDB do Alcinoe abrange as operações de consulta comuns e gere o pooling de ligações nativamente. O cliente simples, TAlMongoDBClient, abre e fecha uma única ligação por operação. A variante com pool, TAlMongoDBConnectionPoolClient, mantém um conjunto de ligações ativas e entrega uma a cada thread de chamada a partir do pool, devolvendo-a quando a chamada termina. Esse modelo evita que múltiplas threads se bloqueiem mutuamente na configuração de ligações, o que é importante quando existem processos em segundo plano a consultar a mesma base de dados em simultâneo. Para cursores tailable em coleções capped, o TAlMongoDBTailMonitoringThread monitoriza novos documentos e aciona um callback quando estes chegam, o que constitui o padrão recomendado para streaming de registos ou notificações de alterações sem sondagem (polling).

Outros componentes importantes

ALVideoPlayer renderiza vídeo para uma TTexture em vez de usar uma janela de sobreposição (overlay), pelo que outros controlos FireMonkey se podem situar acima dele na Z-order. O backend para Android utiliza o ExoPlayer, que adiciona suporte a DASH, HLS e SmoothStreaming além do que o MediaPlayer integrado do Android processa. O backend para iOS utiliza o AVPlayer com suporte HLS equivalente.

TALWebRTC encapsula a stack WebRTC para áudio e vídeo peer-to-peer. Não necessita de um navegador ou de um plugin, e a ligação atravessa NAT através da negociação padrão ICE/STUN/TURN gerida pela biblioteca subjacente.

TALStringList substitui a ordenação baseada em AnsiCompareText da TStringList por uma comparação ordinal independente da localização e um quicksort que é até 10 vezes mais rápido em listas grandes. A variante com tabela de dispersão, TALHashedStringList, adiciona uma tabela de dispersão (hash table) interna para pesquisa em tempo O(1) à custa de uma sobrecarga ligeiramente maior em listas pequenas. Note que o TALStringList é uma lista de AnsiString de 8 bits, não Unicode; adequa-se bem a código do lado do servidor onde o UTF-8 é a codificação de trabalho e o desempenho bruto importa mais do que a comparação sensível à localização.

Em Windows de 64 bits, a herança do FastCode que conferia a muitas das rotinas de strings do Alcinoe a sua vantagem de velocidade (principalmente código assembly x86 escrito à mão) não se aplica. As compilações Win64 recorrem às implementações em Pascal, que funcionam de forma consideravelmente mais lenta em tarefas intensivas de processamento de strings. O projeto demo\ALStringBenchMark permite-lhe medir a diferença no seu hardware antes de optar por uma compilação de 64 bits em que o processamento de strings seja um gargalo.

O código fonte completo encontra-se em github.com/Zeus64/alcinoe.