Техническа статия

Библиотека с компоненти Alcinoe и съвместимост с Delphi 11.1 Alexandria

Alcinoe е библиотека с компоненти с отворен код за Delphi и C++Builder, поддържана в GitHub от Zeus64. Тя покрива области, които VCL и FireMonkey RTL оставят на трети страни: видео плейър с хардуерно ускорение (GPU), WebRTC обвивка, нативни контроли за редактиране в iOS и Android, парсер с двоен режим за JSON/BSON, MongoDB клиент с пул от връзки (connection pooling), обвивка за ImageMagick и колекция от FireMonkey контроли, които напълно заобикалят стандартния конвейер за рендиране. Библиотеката изгради своята репутация на Rio (10.3.3) и Sydney (10.4.2) и оттогава следва всяко издание на Embarcadero. Към момента на писане е напълно съвместима с Delphi 11.1 Alexandria и Delphi Athens 12.3

Включване на Alcinoe в проект

Инсталацията се разделя на един въпрос: имате ли нужда от поддръжка по време на дизайн (design-time) за визуалните контроли на Alcinoe? Ако не, пропуснете BPL изцяло. Добавете {alcinoe_rootdir}\source към пътя за търсене на библиотеки (library search path) на проекта и сте готови. Всеки невизуален компонент, включително парсерите, клиентите за бази данни и инструментите за низове, се компилира от изходния код без да регистрира нищо

Когато ви трябва поддръжка по време на дизайн, пътят е малко по-дълъг. Отворете Component > Install Packages в Delphi IDE, намерете BPL файла, който съответства на вашата версия (например {alcinoe_rootdir}\lib\bpl\alcinoe\Win32\alexandria\Alcinoe_alexandria.bpl), инсталирайте го и след това отново добавете {alcinoe_rootdir}\source към пътя за търсене. BPL регистрира компонентите; директорията с изходния код е това, което компилаторът намира, когато компилира вашия проект

Alcinoe предоставя опционални пачове за изходния код на Embarcadero RTL. Ако ги искате, отидете в {alcinoe_rootdir}\embarcadero\, изберете поддиректорията за вашата версия и стартирайте update.bat. Скриптът очаква GIT в PATH и предполага стандартно местоположение за инсталация на Embarcadero. Той извлича оригиналния изходен код на RTL и прилага пачовете. След като приключи, добавете тази пачната директория с изходен код към пътя за търсене на вашия проект, така че компилаторът да я вземе преди копието само за четене в инсталационното дърво на Embarcadero. Нищо от това не е задължително за започване; има значение само ако се сблъскате с бъгове, които пачовете отстраняват

Android и проксито за D8 дешугъринг

Няколко компонента на Alcinoe (WebRTC, видео базирано на ExoPlayer) зависят от Java библиотеки, които използват езикови функции на Java 8. Инструментите за Android, които идват с по-старите версии на Delphi, използват dx.bat за DEX конвертиране, което не може да се справи с тези байткодове на API нива под 26. Решението е дешугъринг (desugaring), с което D8 се справя автоматично, когато се извика директно. Alcinoe предоставя прокси скрипт в {alcinoe_rootdir}\tools\D8Proxy\dx.bat, който пренасочва извикванията от системата за изграждане на Delphi към D8, правейки дешугъринга прозрачен. Заменете оригиналния dx.bat в директорията build-tools на вашия Android SDK (обикновено C:\SDKs\android\build-tools\30.0.3\) с това прокси. Embarcadero следеше основния проблем като RSP-24155; по-късните версии на SDK инструментите го адресираха директно, така че проверете дали вашият текущ набор от инструменти все още се нуждае от това заобикаляне

Проблемът с рендирането във FireMonkey и отговорът на Alcinoe

Стандартният цикъл на изобразяване на FireMonkey се превръща в тясно място при потребителски интерфейси с интензивно скролиране. Един единствен TRectangle със заоблени ъгли може да отнеме около 3 ms за прерисуване, защото стандартната реализация преизчислява пътя на всеки кадър. При 20 такива видими контроли това прави общо 60 ms на кадър, което ограничава ефективната кадрова честота доста под прага за плавно скролиране

Alcinoe решава това с GPU-резидентен буфер за всяка контрола. Първото изобразяване рендира контролата в TTexture, съхранена в паметта на GPU. Последващите прерисувания копират (blit) тази текстура вместо да изпълняват отново алгоритъма за изобразяване. Измереният резултат при същия заоблен правоъгълник спада от около 3 ms до около 0.1 ms. Отвъд буферирането, Alcinoe заменя чертаенето на пътища чрез OpenGL за основни фигури с нативни API-та за чертаене в Android и iOS, заобикаляйки компромиса между качество и производителност, свързан с Form.Quality. Съответните контроли са TALRectangle, TALCircle и набор от подобрени контейнери за оформление, включително ScrollBox и TabControl

TALJsonDocument: DOM и SAX в един тип

TALJsonDocument е парсерът на Alcinoe за JSON и BSON. Той поддържа два режима на обхождане. DOM режимът изгражда дърво на обектите в паметта, давайки произволен достъп до всеки възел с цената на памет, пропорционална на размера на документа. SAX режимът генерира събития, докато парсерът чете всеки токен без да запазва никакво дърво, което е правилният избор, когато трябва да филтрирате голям документ и да запазите само шепа стойности. DOM парсерите в Delphi (DBXJSON, SuperObject и другите) обикновено са три до пет пъти по-бавни от SAX подхода за същото съдържание, защото всяко заделяне на памет за възел носи режийни разходи за създаване на обект върху самата работа по парсирането

Типът следва същия модел за навигация по възли като TALXMLDocument. Минимално DOM четене изглежда така:

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

// четене на скаларни стойности
ShowMessage(MyJsonDoc.ChildNodes['name'].ChildNodes['first'].Text);
ShowMessage(IntToStr(MyJsonDoc.ChildNodes['_id'].Int32));

// итериране на масив
for I := 0 to MyJsonDoc.ChildNodes['contribs'].ChildNodes.Count - 1 do
  Writeln(MyJsonDoc.ChildNodes['contribs'].ChildNodes[I].Text);

За SAX режим, присвоете анонимна процедура на OnParseText преди да извикате LoadFromJSON с втория аргумент зададен на True. Обратното извикване (callback) получава пътя на възела, името, стойността и TALJSONNodeSubType, който идентифицира JSON типа (низ, цяло число, число с плаваща запетая, булево и т.н.). Този режим не произвежда заделяния в хийпа (heap) за възли, така че мащабира до произволно големи документи без да надхвърля бюджета за памет

TALJsonDocument също така чете и записва BSON нативно; подайте True като BSON флаг на LoadFromFile или SaveToFile. Втори вариант, TALJsonDocumentU, използва UnicodeString (UTF-16) вътрешно вместо AnsiString (UTF-8) за контексти, където обкръжаващият код работи в Unicode навсякъде

MongoDB клиент и пул от връзки

Драйверът за MongoDB на Alcinoe покрива общите операции за заявки и управлява пулове от връзки (connection pooling) нативно. Простият клиент, TAlMongoDBClient, отваря и затваря една връзка за всяка операция. Вариантът с пул, TAlMongoDBConnectionPoolClient, поддържа набор от активни връзки и предава една на всяка извикваща нишка от пула, връщайки я, когато извикването приключи. Този модел предпазва множество нишки от това да се блокират взаимно при установяване на връзка, което е от значение винаги, когато фонови работници (workers) заявяват една и съща база данни едновременно. За tailable курсори върху capped колекции, TAlMongoDBTailMonitoringThread наблюдава за нови документи и изпълнява обратно извикване, когато те пристигнат, което е стандартният модел за стрийминг на логове или известяване за промени без полване (polling)

Други компоненти, които си струва да знаете

ALVideoPlayer рендира видео към TTexture вместо към прозорец-слой (overlay window), така че други FireMonkey контроли могат да стоят над него в Z-реда. Бекендът за Android използва ExoPlayer, който добавя поддръжка за DASH, HLS и SmoothStreaming отвъд това, с което се справя вграденият MediaPlayer на Android. Бекендът за iOS използва AVPlayer с еквивалентна поддръжка за HLS

TALWebRTC обвива WebRTC стека за аудио и видео от точка до точка (peer-to-peer). Той не изисква браузър или плъгин, а връзката преминава през NAT чрез стандартното ICE/STUN/TURN договаряне, което се обработва от базовата библиотека

TALStringList заменя сортирането на TStringList, базирано на AnsiCompareText, с независимо от локала ординално сравнение и quicksort, което е до 10 пъти по-бързо при големи списъци. Хешираният вариант, TALHashedStringList, добавя вътрешна хеш таблица за O(1) търсене с цената на малко по-високи режийни разходи при малки списъци. Имайте предвид, че TALStringList е 8-битов списък с AnsiString, а не Unicode такъв; той пасва добре в код от страна на сървъра, където UTF-8 е работното кодиране и суровата пропускателна способност има по-голямо значение от съобразеното с локала сравнение

При 64-битов Windows, наследството от FastCode, което даде на много от стринговите рутини на Alcinoe тяхното предимство в скоростта (предимно ръчно написан x86 асемблер), не се пренася. Win64 билдовете преминават към Pascal реализациите, които работят забележимо по-бавно при натоварвания с интензивно използване на низове. Проектът demo\ALStringBenchMark ви позволява да измерите разликата на вашия хардуер, преди да се ангажирате с 64-битов билд, където пропускателната способност на низовете е тясно място

Пълният изходен код е в github.com/Zeus64/alcinoe