مقاله فنی

کتابخانه Alcinoe برای Delphi 11.1 Alexandria: راهنمای نصب

Alcinoe یک کتابخانه کامپوننت متن‌باز برای Delphi و C++Builder است که Zeus64 آن را در GitHub نگه‌داری می‌کند. این کتابخانه بخش‌هایی را پوشش می‌دهد که VCL و FireMonkey RTL به کتابخانه‌های شخص ثالث واگذار می‌کنند: پخش‌کننده ویدئوی شتاب‌گرفته با GPU، پوشش WebRTC، کنترل‌های ویرایش بومی iOS و Android، یک پارسر دومسیره JSON/BSON، کلاینت MongoDB با connection pooling، پوشش ImageMagick، و مجموعه‌ای از کنترل‌های FireMonkey که به طور کامل از خط لوله رندر پیش‌فرض عبور می‌کنند. شهرت این کتابخانه با Rio (10.3.3) و Sydney (10.4.2) شکل گرفت و از آن زمان هر انتشار Embarcadero را دنبال کرده است. در زمان نگارش این مطلب، Alcinoe با Delphi 11.1 Alexandria و Delphi Athens 12.3 کاملاً سازگار است

معرفی Alcinoe به پروژه

نصب فقط نیاز به پاسخ به یک سوال دارد: آیا به پشتیبانی زمان طراحی برای کامپوننت‌های بصری Alcinoe نیاز دارید؟ اگر نه، کاملاً از BPL صرف‌نظر کنید. فقط مسیر {alcinoe_rootdir}\source را به مسیر جستجوی کتابخانه پروژه اضافه کنید تا کار تمام شود. هر کامپوننت غیربصری، از جمله تجزیه‌کننده، کلاینت پایگاه داده و ابزارهای رشته، می‌تواند مستقیماً از سورس کد کمپایل شود و نیازی به ثبت هیچ چیزی نیست

وقتی به پشتیبانی design-time نیاز دارید، مسیر کمی طولانی‌تر می‌شود. در Delphi IDE از مسیر Component > Install Packages استفاده کنید، BPL سازگار با نسخه خود را پیدا کنید، برای نمونه {alcinoe_rootdir}\lib\bpl\alcinoe\Win32\alexandria\Alcinoe_alexandria.bpl، آن را نصب کنید، و بعد همچنان {alcinoe_rootdir}\source را به search path اضافه کنید. BPL کامپوننت‌ها را ثبت می‌کند و پوشه source همان جایی است که کامپایلر هنگام ساخت پروژه شما در آن جست‌وجو می‌کند

Alcinoe چند patch اختیاری هم برای سورس‌های Embarcadero RTL ارائه می‌کند. اگر به آن‌ها نیاز دارید، به {alcinoe_rootdir}\embarcadero\ بروید، زیرپوشه مربوط به نسخه خود را انتخاب کنید و update.bat را اجرا کنید. این اسکریپت انتظار دارد GIT در PATH باشد و فرض می‌کند Embarcadero در مسیر پیش‌فرض نصب شده است. اسکریپت سورس اصلی RTL را دریافت می‌کند و patchها را اعمال می‌کند. بعد از پایان کار، آن پوشه patchشده را به search path پروژه اضافه کنید تا کامپایلر آن را پیش از نسخه فقط‌خواندنی داخل درخت نصب Embarcadero پیدا کند. هیچ‌یک از این مراحل برای شروع لازم نیست و فقط وقتی اهمیت پیدا می‌کند که باگ‌هایی را ببینید که این patchها برایشان نوشته شده‌اند

سیستم عامل Android و پروکسی‌های شکرزدایی (desugaring) D8

چند کامپوننت Alcinoe، از جمله WebRTC و ویدئوی متکی بر ExoPlayer، به کتابخانه‌های Java وابسته‌اند که از قابلیت‌های زبانی Java 8 استفاده می‌کنند. زنجیره ابزار Android که همراه نسخه‌های قدیمی Delphi عرضه می‌شود برای تبدیل DEX از dx.bat استفاده می‌کند و این ابزار در API levelهای پایین‌تر از 26 از پس آن bytecodeها برنمی‌آید. راه‌حل desugaring است و D8 وقتی مستقیم فراخوانی شود آن را خودکار انجام می‌دهد. Alcinoe در {alcinoe_rootdir}\tools\D8Proxy\dx.bat یک اسکریپت proxy می‌دهد که فراخوانی‌های سیستم build در Delphi را به D8 می‌فرستد و desugaring را شفاف می‌کند. dx.bat اصلی را در پوشه build-tools از Android SDK، که معمولاً C:\SDKs\android\build-tools\30.0.3\ است، با این proxy جایگزین کنید. Embarcadero این مسئله را با شناسه RSP-24155 دنبال کرده بود؛ نسخه‌های جدیدتر SDK tools آن را به صورت مستقیم حل کرده‌اند، پس بهتر است بررسی کنید زنجیره ابزار فعلی شما هنوز به این workaround نیاز دارد یا نه

مشکلات رندر FireMonkey و راه‌حل‌های Alcinoe

حلقه ترسیم پیش‌فرض FireMonkey در رابط‌های کاربری با اسکرول مکرر تبدیل به یک گلوگاه می‌شود. ترسیم مجدد یک TRectangle با گوشه‌های گرد حدود 3 میلی‌ثانیه طول می‌کشد، زیرا پیاده‌سازی پیش‌فرض در هر فریم مسیر را مجدداً محاسبه می‌کند. اگر 20 کنترل از این دست به طور همزمان قابل مشاهده باشند، در هر فریم در مجموع 60 میلی‌ثانیه زمان هدر می‌رود که نرخ فریم موثر را به مراتب کمتر از آستانه لازم برای اسکرول روان می‌کند

Alcinoe این مشکل را با نگه‌داشتن یک بافر مقیم GPU برای هر کنترل حل می‌کند. نخستین ترسیم، کنترل را به یک TTexture در حافظه GPU رندر می‌کند. در ترسیم‌های بعدی، همان texture مستقیماً blit می‌شود و دیگر الگوریتم paint دوباره اجرا نمی‌شود. نتیجه اندازه‌گیری روی همان مستطیل گوشه‌گرد، افت زمان از حدود 3 ms به حدود 0.1 ms است. فراتر از بافر کردن، Alcinoe برای شکل‌های پایه، مسیرهای OpenGL را با APIهای ترسیم بومی Android و iOS جایگزین می‌کند تا از مصالحه کیفیت و کارایی که به Form.Quality گره خورده عبور کند. کنترل‌های مرتبط شامل TALRectangle، TALCircle و مجموعه‌ای از containerهای layout بهبودیافته از جمله 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، پیش از آن‌که LoadFromJSON را با آرگومان دوم برابر True صدا بزنید، یک anonymous procedure را به OnParseText اختصاص دهید. callback مسیر گره، نام، مقدار، و یک TALJSONNodeSubType را دریافت می‌کند که نوع JSON را مشخص می‌کند، مثل رشته، عدد صحیح، عدد اعشاری، بولین و موارد دیگر. در این حالت برای گره‌ها هیچ تخصیص heap انجام نمی‌شود، پس می‌تواند بدون ترکاندن بودجه حافظه تا هر اندازه سندی مقیاس بگیرد

TALJsonDocument به صورت بومی BSON را هم می‌خواند و می‌نویسد؛ کافی است پرچم BSON را به شکل True به LoadFromFile یا SaveToFile بدهید. گونه دوم، یعنی TALJsonDocumentU، درون خود به جای AnsiString با کدگذاری UTF-8 از UnicodeString با UTF-16 استفاده می‌کند و برای بافت‌هایی مناسب است که کل کد پیرامونی از ابتدا تا انتها Unicode کار می‌کند

کلاینت MongoDB و مخزن اتصالات (connection pool)

درایور MongoDB در Alcinoe عملیات متداول پرس‌وجو را پوشش می‌دهد و connection pooling را هم به صورت بومی مدیریت می‌کند. کلاینت ساده، یعنی TAlMongoDBClient، برای هر عملیات یک اتصال را باز و بسته می‌کند. گونه pooled با نام TAlMongoDBConnectionPoolClient مجموعه‌ای از اتصال‌های زنده را نگه می‌دارد و از pool به هر thread فراخوان یک اتصال می‌دهد و بعد از اتمام فراخوان آن را برمی‌گرداند. این مدل مانع می‌شود threadهای متعدد هنگام برقراری اتصال همدیگر را سد کنند و وقتی workerهای پس‌زمینه هم‌زمان روی یک پایگاه داده پرس‌وجو می‌زنند اهمیت زیادی پیدا می‌کند. برای tailable cursor روی capped collectionها، کلاس TAlMongoDBTailMonitoringThread ورود سندهای جدید را پایش می‌کند و با رسیدن آن‌ها callback را صدا می‌زند، که الگوی رایج برای log streaming یا change notification بدون polling است

سایر کامپوننت‌هایی که ارزش شناختن دارند

ALVideoPlayer ویدئو را به جای یک پنجره overlay در یک TTexture رندر می‌کند، بنابراین کنترل‌های دیگر FireMonkey می‌توانند در Z-order روی آن بنشینند. backend مربوط به Android از ExoPlayer استفاده می‌کند و پشتیبانی از DASH، HLS و SmoothStreaming را فراتر از توان MediaPlayer داخلی Android اضافه می‌کند. backend iOS هم از AVPlayer با پشتیبانی هم‌ارز HLS بهره می‌برد

کلاس TALWebRTC استک WebRTC را برای صدا و تصویر همتا به همتا (P2P) کپسوله‌سازی می‌کند;بدون نیاز به مرورگر یا افزونه، اتصال از طریق مذاکرات استاندارد ICE/STUN/TURN که توسط کتابخانه زیرین مدیریت می‌شود، از NAT عبور می‌کند

کلاس TALStringList مرتب‌سازی مبتنی بر AnsiCompareText در TStringList را با مقایسه ترتیبی مستقل از لوکال و مرتب‌سازی سریع با بهبود سرعت تا 10 برابر جایگزین می‌کند;نسخه هش‌شده یعنی TALHashedStringList یک جدول هش داخلی اضافه می‌کند تا جستجوی O(1) را به قیمت هزینه کمی بالاتر در لیست‌های کوچک محقق کند;توجه داشته باشید که TALStringList لیستی از AnsiString 8 بیتی است، نه یک لیست Unicode;این کلاس برای کدهای سمت سرور بسیار مناسب است,جایی که UTF-8 کدگذاری کاری است و پهنای باند خام مهم‌تر از مقایسه حساس به منطقه است

روی Windows 64 بیتی، میراث FastCode که به بسیاری از روتین‌های رشته‌ای Alcinoe برتری سرعت می‌داد، که بیشتر متکی به اسمبلی x86 دست‌نویس بود، دیگر منتقل نمی‌شود. buildهای Win64 به پیاده‌سازی‌های Pascal برمی‌گردند و در workloadهای رشته‌محور به شکل محسوسی کندتر هستند. پروژه demo\ALStringBenchMark به شما اجازه می‌دهد این فاصله را روی سخت‌افزار خودتان اندازه بگیرید و اگر throughput رشته‌ای گلوگاه است، درباره build 64 بیتی با آگاهی تصمیم بگیرید

سورس کد کامل در آدرس github.com/Zeus64/alcinoe قرار دارد