Alcinoe هي مكتبة مكونات مفتوحة المصدر لـ Delphi و C++Builder، ويطوّرها Zeus64 على GitHub. وهي تغطي ما تتركه 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 إلى مشروع
يتوقف التثبيت على سؤال واحد: هل تحتاج إلى دعم وقت التصميم للعناصر المرئية في Alcinoe؟ إذا لم تكن بحاجة إليه، فتجاوز ملف BPL بالكامل. أضف {alcinoe_rootdir}\source إلى مسار البحث في مكتبة المشروع، وبذلك تكون انتهيت. كل المكونات غير المرئية، بما فيها المحللات وعمليات الاتصال بقاعدة البيانات وأدوات السلاسل، تُترجم من المصدر من دون تسجيل أي شيء.
عندما تحتاج إلى دعم وقت التصميم، يصبح المسار أطول قليلًا. افتح Component > Install Packages في Delphi IDE، واستعرض ملف BPL المطابق لإصدارك، مثل {alcinoe_rootdir}\lib\bpl\alcinoe\Win32\alexandria\Alcinoe_alexandria.bpl، ثم ثبته، وبعد ذلك أضف أيضًا {alcinoe_rootdir}\source إلى مسار البحث. ملف BPL يسجل المكونات، أما مجلد المصدر فهو ما يجده المترجم عندما يترجم مشروعك.
تُرفق Alcinoe تصحيحات اختيارية لمصادر RTL الخاصة بـ Embarcadero. إذا أردتها، فانتقل إلى {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 الأصلي في مجلد build-tools الخاص بـ Android SDK، وعادة يكون C:\SDKs\android\build-tools\30.0.3\، بهذا الوكيل. وقد تتبعت Embarcadero المشكلة الأساسية تحت RSP-24155، بينما عالجتها إصدارات لاحقة من أدوات SDK مباشرة، لذا تحقّق مما إذا كانت سلسلة الأدوات لديك لا تزال تحتاج إلى هذا الالتفاف.
مشكلة العرض في FireMonkey وحل Alcinoe
تتحول دورة الرسم الافتراضية في FireMonkey إلى عنق زجاجة في واجهات المستخدم كثيفة التمرير. يمكن أن يستغرق TRectangle واحد بزوايا مستديرة نحو 3 مللي ثانية لإعادة الرسم، لأن التنفيذ القياسي يعيد حساب المسار في كل إطار. ومع وجود 20 عنصرًا من هذا النوع ظاهرًا، يرتفع الزمن إلى 60 مللي ثانية لكل مرور إطار، وهو ما يضع معدل الإطارات الفعلي دون عتبة التمرير السلس بكثير.
تعالج Alcinoe هذا بمخزن مؤقت مقيم في GPU لكل عنصر تحكم. أول عملية رسم تعرض العنصر إلى TTexture مخزنة في ذاكرة GPU. أما عمليات الرسم اللاحقة فتنفذ blit لتلك الصورة بدل إعادة تنفيذ خوارزمية الرسم. والنتيجة المقاسة على المستطيل ذي الزوايا المستديرة نفسه تهبط من نحو 3 مللي ثانية إلى نحو 0.1 مللي ثانية. وإلى جانب التخزين المؤقت، تستبدل Alcinoe رسم المسارات عبر OpenGL للأشكال الأساسية بواجهات الرسم الأصلية في 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 كوسيط ثانٍ. يتلقى ردّ النداء مسار العقدة واسمها وقيمتها، إضافة إلى TALJSONNodeSubType الذي يحدد نوع JSON: نص، عدد صحيح، عدد عشري، قيمة منطقية، وغير ذلك. هذا النمط لا يخصّص أي كائنات في الكومة للعقد، لذلك يتوسع إلى مستندات ضخمة جدًا من دون استهلاك ميزانية الذاكرة.
TALJsonDocument يقرأ ويكتب BSON أصليًا أيضًا؛ مرّر True كعلم BSON إلى LoadFromFile أو SaveToFile. أما النسخة الثانية، TALJsonDocumentU، فتستخدم UnicodeString (UTF-16) داخليًا بدل AnsiString (UTF-8) في الحالات التي يعمل فيها الكود المحيط كله بترميز Unicode.
عميل MongoDB وتجميع الاتصالات
يغطي برنامج تشغيل MongoDB في Alcinoe عمليات الاستعلام الشائعة ويتعامل مع تجميع الاتصالات أصليًا. العميل البسيط، TAlMongoDBClient، يفتح اتصالًا واحدًا ويغلقه مع كل عملية. أما النسخة المجمعّة، TAlMongoDBConnectionPoolClient، فتحافظ على مجموعة من الاتصالات الحية وتمنح كل خيط مستدعي اتصالًا من المجموعة، ثم تعيده عند اكتمال الاستدعاء. هذا النموذج يمنع عدة خيوط من حجب بعضها أثناء إعداد الاتصال، وهو أمر مهم عندما تستعلم مهام الخلفية قاعدة البيانات نفسها في الوقت نفسه. وبالنسبة إلى المؤشرات اللاحقة على المجموعات المحدودة، يراقب TAlMongoDBTailMonitoringThread المستندات الجديدة ويطلق ردّ النداء عندما تصل، وهو النمط القياسي لبث السجلات أو إشعارات التغيير من دون استقصاء متكرر.
مكونات أخرى تستحق المعرفة
ALVideoPlayer يعرض الفيديو إلى TTexture بدلًا من نافذة تراكب، بحيث يمكن لعناصر FireMonkey الأخرى أن تظهر فوقه في ترتيب Z. تستخدم الواجهة الخلفية في Android ExoPlayer، ما يضيف دعم DASH وHLS وSmoothStreaming إلى ما يتعامل معه MediaPlayer المدمج في Android. أما الواجهة الخلفية في iOS فتستخدم AVPlayer مع دعم HLS مماثل.
TALWebRTC يغلّف حزمة WebRTC للصوت والفيديو بين الأقران. ولا يحتاج إلى متصفح أو إضافة، كما يجتاز الاتصال NAT عبر تفاوض ICE/STUN/TURN القياسي الذي تتكفل به المكتبة الأساسية.
TALStringList يستبدل الفرز القائم على AnsiCompareText في TStringList بمقارنة ترتيبية مستقلة عن الإعدادات المحلية وبخوارزمية quicksort أسرع بما يصل إلى 10 مرات على القوائم الكبيرة. أما النسخة المعتمدة على التجزئة، TALHashedStringList، فتضيف جدول تجزئة داخليًا للبحث بزمن O(1)، مقابل كلفة أعلى قليلًا على القوائم الصغيرة. لاحظ أن TALStringList هي قائمة AnsiString بطول 8 بت، وليست قائمة Unicode؛ وهي مناسبة جدًا لكود الخادم عندما يكون UTF-8 هو ترميز العمل ويصبح معدل النقل الخام أهم من المقارنة الحساسة للغة.
على Windows 64-bit، لا ينتقل إرث FastCode الذي منح كثيرًا من روتينات السلاسل في Alcinoe سرعتها، ومعظمه كان تجميع x86 مكتوبًا يدويًا، إلى هذا العالم. تعتمد إصدارات Win64 على تطبيقات Pascal البديلة، وهي أبطأ بوضوح في الأحمال الكثيفة بالسلاسل. يتيح لك مشروع demo\ALStringBenchMark قياس الفجوة على جهازك قبل اعتماد بنية 64-bit يكون فيها معدل معالجة السلاسل عنق الزجاجة.
المصدر الكامل موجود على github.com/Zeus64/alcinoe.