تخطي إلى المحتوى
Corex
كل المقالات

ما سرعة Checkbox API؟ العب Tetrex واكتشف.

أردت أن أعرف ماذا يفعل checkbox Corex تحت الإساءة. فصنعت لعبة صغيرة من 180 منها وراقبتها تنجو.

أردت أن أعرف ماذا يفعل checkbox Corex تحت الإساءة. ليس في benchmark حيث يمكنك تلاعب الأرقام، بل في شاشة حقيقية حيث تتغيّر حالة checkboxes كثيرة عدة مرات في الثانية، مع دعم لوحة مفاتيح كامل، ARIA كامل، إدارة تركيز كاملة، كلها تعمل معاً. احتجت اختباراً يشعر به العين.

فصنعت لعبة صغيرة.

Tetrex شبكة 10 في 18 من checkboxes Corex تتظاهر بأنها قطع Tetris. كل خلية <.checkbox> حقيقي، بـ id ثابت، ونفس آلة Zag تحت كل checkbox آخر في المكتبة، ومعدّلات BEM للون القطعة (checkbox--accent للقطعة accent، checkbox--info لـ info، وهكذا). عندما تسقط قطعة، تضيء الخلايا. عندما يُمسح صف، ينهار. عندما تنتهي اللعبة، يسجل النتيجة في لوحة متصدرين مدعومة بـ Phoenix Presence وGenServer لكل لعبة.

يمكنك لعبها في /ar/showcases/tetrex. محرك اللعبة نفسه JavaScript عادي في tetrex_engine.js. الجزء المثير ليس كيف يعمل المحرك. الجزء المثير ماذا يحدث لـ 180 checkbox يمكن الوصول إليها عندما تعامل الشاشة كـ frame buffer.

لماذا لعبة، بدل benchmark

الـ benchmarks تخبرك ماذا يستطيع المكوّن معزولاً. مفيدة كاختبار وحدة: ضرورية، لكن غير كافية. شاشة حقيقية فيها مكوّنات كثيرة وضغطات مفاتيح كثيرة وpatches DOM كثيرة، ومستخدم يلاحظ اللحظة التي يتباطأ فيها أي منها ببضع ميلي ثانية في المكان الخطأ.

اللعبة فيها كل ذلك عن قصد. تلعب بمفاتيح الأسهم. تمسك قطعة بينما تسقط أخرى. تمسح صفوفاً والموسيقى تعزف. إن كان hook الـ checkbox بطيئاً في أي من تلك اللحظات، تشعر قبل أن تقيس. العين أقسى profiler استخدمته، ولا يهتم بما يقوله الـ console.

Tetrex ليس اختبار إجهاد مصطنع. هو اختبار إجهاد يمكنك أن تخسر فيه.

طريقتان لقيادة checkbox

Corex يكشف مسارين لضبط حالة checkbox من خارج نقر المستخدم. يصلان لنفس المكان في الآلة. الفرق من أين تأتي التعليمات.

مسار الخادم هو Corex.Checkbox.set_checked/3 والمساعدات حوله. يدفع حدث hook عبر WebSocket. الـ hook يستقبل الحدث في المتصفح ويستدعي آلة Zag. هذا المسار الذي يعلّمه عرض API في /ar/checkbox/api، وهو المسار الصحيح لمعظم التطبيقات. يبقي الخادم مسؤولاً.

مسار العميل حدث DOM يستمع له الـ hook على الجذر. ترسل corex:checkbox:set-checked مخصصاً مع تفصيل checked، ونفس الـ hook يتفاعل بنفس الطريقة، لكن الرحلة محلية. لا socket. لا diff. لا قفزة خادم.

root.dispatchEvent(
 new CustomEvent("corex:checkbox:set-checked", {
 bubbles: false,
 detail: { checked: nextChecked }
 })
)

في Tetrex، كل تحديث خلية في كل إطار يستخدم مسار العميل. لا سيناريو حيث القطعة الساقطة تتحمل round trip. الـ hook هناك، على نفس العنصر الذي يريد المحرك تغييره. تخطي الـ socket هو الشيء الوحيد المعقول.

ما ما زال يذهب للخادم

اللعب محلي. النتيجة ليست.

عندما تنتهي اللعبة، المحرك يرسل لقطة مضغوطة من الإطارات إلى LiveView. GenServer لكل لعبة (E2e.Tetrex.Session) يلتقطها. يضيف إلى شريط replay، مُعيَّن ومحدود حتى لا تنمو لعبة طويلة بلا حد. يبث تحديثات للمشاهدين على موضوع tetrex:session:<id>، فيشترك المتفرجون. عندما تؤهل الجولة لأفضل عشرة، يحفظ النتيجة والإطارات في قاعدة البيانات.

إذن للتقسيم اسم: الطلاء محلي، السجلات للخادم. أي شيء تريد مشاركته أو إعادة تشغيله أو تدقيقه أو ترتيبه يعبر السلك بجدول لا يلاحظه المستخدم.

بما في ذلك مسار replay نفسه. إن أنهيت جولة في أفضل عشرة، يمكن للخادم أن يعطيك قائمة إطارات حتمية ونفس hook GameBoard يمشي في التاريخ، مرسلاً نفس أحداث corex:checkbox:set-checked التي استخدمها المحرك الحي. زر replay نفسه toggle من Corex، يُقاد عبر corex:toggle:set-pressed على جذر الـ toggle. نفس نمط حدث العميل، مكوّن مختلف.

Presence وGenServer ومخزن، ثلاثة مصادر حية

لوحة المتصدرين تدمج ثلاث تغذيات.

Registry يحتفظ بقائمة ids الجلسات النشطة ونتائجها الحالية، ليعرض الردهة من يلعب الآن.

Phoenix.Presence يتتبع من في الردهة ومن يشاهد أي لعبة، لتبقى أعداد المتفرجين حية.

مخزن يحتفظ بأفضل عشرة محفوظة مع replays إطاراتها.

عند انتهاء اللعبة، Store.finalize/4 يكتب اللعبة، يقلّص لعشرة مدخلات، ويبث :leaderboard_updated. كل LiveView يراقب الردهة يعدّل جدوله دون إعادة تحميل كاملة.

كل مصدر له جواب صادق. Presence تجيب «من حي الآن؟». GenServer يجيب «ماذا حدث في id هذه اللعبة؟». قاعدة البيانات تجيب «ما أفضل عشر جولات احتفظنا بها؟». الشاشة تجمع الثلاثة دون الخلط بينها.

ماذا يثبت هذا عن checkbox

ما أردت معرفته عندما بنيت Tetrex: هل يمكن استخدام مكوّن يمكن الوصول إليه حقاً بإيقاع غير عادي. الجواب نعم، بتحفظ واحد: خذ مسار العميل عندما تحتاج الإيقاع، ومسار الخادم عندما تحتاج السلطة.

الـ 180 خلية كلها مكوّنات حقيقية. لها نفس قواعد التركيز، نفس aria-checked، نفس دعم لوحة المفاتيح لـ checkbox واحد. يمكنك Tab لإحداها وتبديلها يدوياً إن أردت. الشبكة ليست <canvas> تتظاهر بإمكانية الوصول؛ هي كذلك فعلاً. ذلك الجزء لا يكلف شيئاً إضافياً في وقت التشغيل، لأن آلة Zag لا تهتم إن كانت الوحيدة في الصفحة أو المئة.

أيضاً لا تدفع ثمن سطح API الذي لا تستخدمه. مسارا العميل والخادم موجودان لكل checkbox في تطبيقك. Tetrex يستخدم أحدهما بقوة أكثر مما ستفعله معظم التطبيقات.

ملاحظة صغيرة عن الإيقاع

هناك شيء في Tetrex أجده مفيداً كتذكير. الـ 180 checkbox ليست مقياساً غير عادي. لوحات تحكم حقيقية كثيرة فيها عقد تفاعلية أكثر على الشاشة. غير العادي هو معدل تغيّرها. كل إطار قد يلمس عشرات الخلايا، وهناك إطارات كثيرة في الثانية. النمط الذي ينجو من ذلك الحمل ليس «أعد رسم الشبكة كاملة من HEEx عند كل tick». بل «دع الآلة تعرف كل خلية، ثم حدّث الخلايا في مكانها عندما يدق المحرك».

إن بنيت لوحة Phoenix تحدّث بلاطات كثيرة في الثانية، نفس التقسيم سيفيد. ارسم الخلايا مرة. حدّثها في مكانها عبر مسار العميل. أرسل للخادم ما لا تستطيع تحمل فقده.

كيف يرتبط ببقية Corex

التشريح يشرح لماذا يمكن أن تكون كل خلية checkbox عادي بمعدّلات، لا عنصراً مخصصاً مخترعاً.

عقلان يشرح كيف تبقى الآلة والخادم بعيدين عن بعضهما عندما يهتم الاثنان بنفس التحكم.

آلة Vanilla JS يشرح كيف يتحمل الـ hook استدعاءات كثيرة في الثانية دون فقدان الحالة، بفضل قدرة updateProps التي هبطت في محول vanilla لـ Zag.

صفحة Checkbox API تمشي في النسخة المدفوعة من الخادم لنفس سطح setChecked الذي يستدعيه Tetrex من JavaScript.

إن كنت تقيّم Corex للوحات أو ألعاب أو أي شيء يقلب حالة تحكم كثيرة في الثانية، Tetrex هو النسخة الصريحة من السؤال. العب جولة. انظر إن كانت تواكب.