Artykuł techniczny

Biblioteka komponentów Alcinoe a zgodność z Delphi 11.1 Alexandria

Alcinoe to otwartoźródłowa biblioteka komponentów dla Delphi i C++Builder, utrzymywana na GitHubie przez Zeus64. Pokrywa obszar, który VCL i RTL FireMonkey pozostawiają podmiotom trzecim: przyspieszany na GPU odtwarzacz wideo, opakowanie WebRTC, natywne kontrolki edycji dla iOS i Androida, dwutrybowy parser JSON/BSON, klient MongoDB z pulą połączeń, opakowanie ImageMagick oraz zestaw kontrolek FireMonkey, które całkowicie omijają standardowy potok renderowania. Biblioteka zbudowała swoją reputację na Rio (10.3.3) i Sydney (10.4.2), a od tamtej pory nadąża za każdym wydaniem Embarcadero. W chwili pisania jest w pełni zgodna z Delphi 11.1 Alexandria oraz Delphi Athens 12.3

Wprowadzanie Alcinoe do projektu

Instalacja rozgałęzia się na jednym pytaniu: czy potrzebujesz wsparcia w czasie projektowania (design-time) dla wizualnych kontrolek Alcinoe? Jeśli nie, pomiń BPL w całości. Dodaj {alcinoe_rootdir}\source do ścieżki wyszukiwania bibliotek projektu i gotowe. Każdy niewizualny komponent, w tym parsery, klienty baz danych i narzędzia do obsługi ciągów znaków, kompiluje się ze źródła bez rejestrowania czegokolwiek

Gdy jednak potrzebujesz wsparcia w czasie projektowania, ścieżka jest nieco dłuższa. Otwórz Component > Install Packages w IDE Delphi, przejdź do BPL pasującego do twojej wersji (na przykład {alcinoe_rootdir}\lib\bpl\alcinoe\Win32\alexandria\Alcinoe_alexandria.bpl), zainstaluj go, a następnie mimo wszystko dodaj {alcinoe_rootdir}\source do ścieżki wyszukiwania. BPL rejestruje komponenty; katalog źródeł jest tym, co kompilator znajduje podczas kompilowania twojego projektu

Alcinoe dostarcza opcjonalne łatki (patches) do źródeł RTL Embarcadero. Jeśli ich chcesz, przejdź do {alcinoe_rootdir}\embarcadero\, wybierz podkatalog dla swojej wersji i uruchom update.bat. Skrypt oczekuje GIT w PATH i zakłada domyślną lokalizację instalacji Embarcadero. Pobiera oryginalne źródło RTL i nakłada łatki. Po zakończeniu dodaj ten załatany katalog źródeł do ścieżki wyszukiwania projektu, aby kompilator sięgał po niego przed kopią tylko do odczytu w drzewie instalacyjnym Embarcadero. Nic z tego nie jest wymagane do rozpoczęcia pracy; ma to znaczenie tylko wtedy, gdy natkniesz się na błędy, które łatki adresują

Android i pośrednik desugaringu D8

Kilka komponentów Alcinoe (WebRTC, wideo oparte na ExoPlayer) zależy od bibliotek Java, które korzystają z funkcji językowych Java 8. Łańcuch narzędzi Androida dostarczany ze starszymi wersjami Delphi używa dx.bat do konwersji DEX, który nie radzi sobie z tymi kodami bajtowymi na poziomach API poniżej 26. Rozwiązaniem jest desugaring, który D8 obsługuje automatycznie po bezpośrednim wywołaniu. Alcinoe dostarcza skrypt pośredniczący pod adresem {alcinoe_rootdir}\tools\D8Proxy\dx.bat, który przekazuje wywołania z systemu budowania Delphi do D8, czyniąc desugaring przezroczystym. Zastąp oryginalny dx.bat w katalogu build-tools swojego Android SDK (zazwyczaj C:\SDKs\android\build-tools\30.0.3\) tym pośrednikiem. Embarcadero śledziło problem u podstaw pod numerem RSP-24155; późniejsze wersje narzędzi SDK rozwiązały go bezpośrednio, więc sprawdź, czy twój obecny łańcuch narzędzi wciąż wymaga tego obejścia

Problem renderowania FireMonkey i odpowiedź Alcinoe

Domyślny cykl malowania FireMonkey staje się wąskim gardłem w interfejsach z intensywnym przewijaniem. Pojedynczy TRectangle z zaokrąglonymi rogami może zająć około 3 ms na przemalowanie, ponieważ standardowa implementacja przelicza ścieżkę na każdej klatce. Przy 20 takich widocznych kontrolkach daje to 60 ms na jedno przejście malowania, co ogranicza efektywną liczbę klatek na sekundę znacznie poniżej progu płynnego przewijania

Alcinoe rozwiązuje to za pomocą rezydentnego bufora na GPU dla każdej kontrolki. Pierwsze malowanie renderuje kontrolkę do TTexture przechowywanej w pamięci GPU. Kolejne przemalowania kopiują (blit) tę teksturę zamiast ponownie wykonywać algorytm malowania. Zmierzony wynik na tym samym zaokrąglonym prostokącie spada z około 3 ms do około 0,1 ms. Poza buforowaniem Alcinoe zastępuje rysowanie ścieżek OpenGL dla podstawowych kształtów natywnymi API rysowania Androida i iOS, omijając kompromis jakość/wydajność związany z Form.Quality. Istotne kontrolki to TALRectangle, TALCircle oraz zestaw ulepszonych kontenerów układu, w tym ScrollBox i TabControl

TALJsonDocument: DOM i SAX w jednym typie

TALJsonDocument to parser JSON i BSON Alcinoe. Obsługuje dwa tryby przechodzenia. Tryb DOM buduje w pamięci drzewo obiektów, dając losowy dostęp do dowolnego węzła kosztem pamięci proporcjonalnej do rozmiaru dokumentu. Tryb SAX uruchamia zdarzenia w miarę odczytywania przez parser każdego tokenu bez zachowywania jakiegokolwiek drzewa, co jest właściwym wyborem, gdy trzeba przefiltrować duży dokument i zachować tylko garść wartości. Parsery DOM w Delphi (DBXJSON, SuperObject i pozostałe) są zazwyczaj od trzech do pięciu razy wolniejsze niż podejście SAX dla tej samej zawartości, ponieważ każda alokacja węzła niesie ze sobą narzut tworzenia obiektu ponad samą pracą parsowania

Typ podąża za tym samym wzorcem nawigacji po węzłach co TALXMLDocument. Minimalny odczyt DOM wygląda tak:

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

W trybie SAX przypisz procedurę anonimową do OnParseText przed wywołaniem LoadFromJSON z drugim argumentem ustawionym na True. Wywołanie zwrotne otrzymuje ścieżkę węzła, nazwę, wartość oraz TALJSONNodeSubType, który identyfikuje typ JSON (string, integer, float, boolean i tak dalej). Ten tryb nie generuje żadnych alokacji na stercie dla węzłów, więc skaluje się do dowolnie dużych dokumentów bez wysadzania budżetu pamięci

TALJsonDocument odczytuje i zapisuje również natywnie BSON; przekaż True jako flagę BSON do LoadFromFile lub SaveToFile. Drugi wariant, TALJsonDocumentU, używa wewnętrznie UnicodeString (UTF-16) zamiast AnsiString (UTF-8) w kontekstach, gdzie otaczający kod pracuje na Unicode w całości

Klient MongoDB i pula połączeń

Sterownik MongoDB Alcinoe pokrywa typowe operacje zapytań i obsługuje pulę połączeń natywnie. Prosty klient, TAlMongoDBClient, otwiera i zamyka jedno połączenie na operację. Wariant z pulą, TAlMongoDBConnectionPoolClient, utrzymuje zestaw aktywnych połączeń i przekazuje jedno każdemu wywołującemu wątkowi z puli, zwracając je po zakończeniu wywołania. Ten model powstrzymuje wiele wątków przed wzajemnym blokowaniem się przy zestawianiu połączeń, co ma znaczenie zawsze, gdy zadania w tle odpytują tę samą bazę danych jednocześnie. Dla kursorów tailable na kolekcjach capped, TAlMongoDBTailMonitoringThread obserwuje nowe dokumenty i uruchamia wywołanie zwrotne, gdy się pojawiają, co jest standardowym wzorcem strumieniowania logów lub powiadamiania o zmianach bez odpytywania

Inne komponenty warte poznania

ALVideoPlayer renderuje wideo do TTexture zamiast do okna nakładki (overlay), więc inne kontrolki FireMonkey mogą znajdować się nad nim w kolejności Z. Backend Androida używa ExoPlayer, który dodaje wsparcie DASH, HLS i SmoothStreaming ponad to, co obsługuje wbudowany MediaPlayer Androida. Backend iOS używa AVPlayer z równoważnym wsparciem HLS

TALWebRTC opakowuje stos WebRTC dla dźwięku i wideo peer-to-peer. Nie wymaga przeglądarki ani wtyczki, a połączenie przechodzi przez NAT dzięki standardowej negocjacji ICE/STUN/TURN, którą obsługuje biblioteka bazowa

TALStringList zastępuje oparte na AnsiCompareText sortowanie TStringList porównaniem porządkowym niezależnym od ustawień regionalnych oraz quicksortem, który jest nawet do 10x szybszy na dużych listach. Wariant z haszowaniem, TALHashedStringList, dodaje wewnętrzną tablicę haszującą dla wyszukiwania O(1) kosztem nieco wyższego narzutu na małych listach. Zwróć uwagę, że TALStringList to 8-bitowa lista AnsiString, a nie Unicode; dobrze pasuje do kodu po stronie serwera, gdzie UTF-8 jest kodowaniem roboczym, a surowa przepustowość liczy się bardziej niż porównanie świadome ustawień regionalnych

Na 64-bitowym Windows dziedzictwo FastCode, które dało wielu procedurom obsługi ciągów Alcinoe ich przewagę szybkości (głównie ręcznie pisany asembler x86), nie przenosi się dalej. Kompilacje Win64 wracają do implementacji w Pascalu, które działają zauważalnie wolniej przy obciążeniach intensywnie korzystających z ciągów. Projekt demo\ALStringBenchMark pozwala zmierzyć różnicę na twoim sprzęcie, zanim zdecydujesz się na kompilację 64-bitową tam, gdzie przepustowość ciągów jest wąskim gardłem

Pełne źródło znajduje się pod adresem github.com/Zeus64/alcinoe