Technical Article

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

Alcinoe е библиотека с компоненти с отворен код за Delphi и C++Builder, разработвана в GitHub от Zeus64. Тя покрива области, които VCL и FireMonkey RTL оставят на външни доставчици: видео плейър с GPU ускорение, WebRTC обвивка, оригинални iOS и Android контролки за въвеждане на текст, двурежимен JSON/BSON парсер, MongoDB клиент с поддържане на пул от връзки, 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 предлага незадължителни пачове (patches) за изходния код на Embarcadero RTL. Ако искате да ги приложите, отидете на {alcinoe_rootdir}\embarcadero\, изберете поддиректорията за вашата версия и стартирайте update.bat. Скриптът изисква GIT да бъде добавен в системния път (PATH) и предполага стандартна инсталация на Embarcadero. Той извлича оригиналния RTL изходен код и прилага пачовете. След като приключите, добавете тази пачната директория към пътя за търсене на вашия проект, за да може компилаторът да я използва преди копието в инсталационното дърво на Embarcadero. Нищо от това не е задължително за начална работа; то е важно само ако се сблъскате с проблеми, които пачовете отстраняват.

Android и проксито за D8 desugaring

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

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

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

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

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

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

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

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);

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

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

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

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

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

ALVideoPlayer рендерира видео в TTexture, а не в наслагващ се прозорец (overlay window), така че други FireMonkey компоненти могат да се разполагат над него по Z-ред. Задната част (backend) за Android използва ExoPlayer, който добавя поддръжка за DASH, HLS и SmoothStreaming над възможностите на вградения в Android MediaPlayer. Задната част за 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.