السلام عليكم ورحمة الله،
من تسجيل لحوار يدور بين بعض المبرمجين في شركة ما:
مبرمج 1: وكما كنت أقول لكم، هذا المحرك سيكون له شأن كبير. كل ما علينا هو إتمام تصميم المزايا المتبقية…
مبرمج 2: مثل نظام البريمجات (scripting system) مثلاً…
(همهمات تتصاعد بين المبرمجين، تؤيد ما قاله المبرمج 2)
مبرمج 1: حسناً، نحتاج إلى بناء نظام بريمجات يتيح السرعة في التجريب والسهولة في التعامل، وتكون لغته سهلة لغير المبرمجين.
مبرمج 3: بما أننا سنبني لغة خاصة بنا هنا، فنستطيع إدخال مفاهيم جديدة تساعد في تسهيل المهمة على المستخدم، مثل عدم الحاجة للتعامل مع الذاكرة على الإطلاق. الحجز والتحرير يتم تلقائياً باستخدام جامع قمامة (garbage collector).
مبرمج 4: (يتهكم) الفكرة جيدة، لكنها ليست جديدة كما تعلم…
مبرمج 2: ولم لا نستخدم نظام جاهز مثل لوا (LUA)؟
… الآن دعوني أوقف هذا التسجيل وأستعيد زمام الحديث… في الحقيقة، الحوار السابق تستطيع سماعه في كل شركة تقرر بناء محرك ألعاب خاص بها. فالموضوع هذه الأيام بديهي للغاية، وأعتقد أن مجرد التصريح بأنك لن تدرج نظام بريمجات في محركك سيجعلك في نظر الأغلبية مخبولاً ترتدي مقلاة على رأسك وتقرع بها وأنت تسير في الشارع. لكن لنتمهل قليلاً قبل الحكم، ولنأخذ الأمور بروية بعيداً عن حملات التبشير. لم لا نبدأ من حيث يجب أن نبدأ، ونتحدث عما هو نظام البريمجات وما الحاجة له…
نظام البريمجات – نظرة وردية
في تطوير الألعاب، نظام البريمجات هو إحدى الأدوات التي تتيح تشغيل بريمجات (scripts) في اللعبة. البريمجات هي نصوص برمجية قصيرة لتنفيذ مهمة محددة. مثلاً، يمكن كتابة بريمج لإجبار شخصية في اللعبة على التحرك بنسق معين عند حدث معين في المرحلة.
يذكر زميلي أنس مصطفاوي واحداً من أبرز الأسباب في انتشار أنظمة البريمجات في الألعاب. يقول أنس:
بما ان البرامج زاد تعقيدها و حجمها، فان زمن بناءها ارتفع بشكل مزعج ( من عدة دقائق، الى عدة ساعات احيانا في البرامج الضخمة) مما كان يؤدي الى هدر مكلف جدا للوقت. خصوصا اذا تم تغير سطر واحد في الكود، ثم قضاء 30دقيقة لرؤية النتيجة
لكن المسألة أوسع من هذا، وتنتمي إلى نقاش فلسفي بعنوان "الكود كبيانات" (code as data). باختصار، التعامل مع الكود كمورد آخر في البرنامج مثله مثل أية صورة أو ملف صوتي أو مجسم أو أي شيء آخر… تستطيع تحميله متى شئت وتنفيذه، ولا تحتاج إلى إعادة ترجمة وتجميع برنامجك عند كل تعديل على هذا المورد. لكن دعونا من الفلسفة الآن، ولنسأل ما فائدة مثل هذا النظام لمطوري الألعاب؟ القائمة أدناه تجمع أهم الفوائد:
- بسبب صغر حجم هذه البريمجات، فإنها لا تستغرق وقتاً طويلاً عند معالجتها وترجمتها. هذا يؤدي إلى أن إجراء تعديل في بريمج ورؤية نتيجته يستغرق زمناً أقل مقارنة مع نفس التعديلات لو أجريت على كود اللعبة الأصلي (هذا هو فعلاً أهم الأسباب). يصطلح على هذه الخاصية اسم زمن التكرار (iteration time).
- تبسيط بناء منطق اللعبة وجعل هذه المهمة متاحة لغير المبرمجين (المصممين والمخططين). يتم ذلك عن طريق جعل اللغة التي يـَـكتب بها البريمج لغة بسيطة تبتعد عن التعقيدات الموجودة في لغات مثل سي بلس بلس. في بعض الحالات أيضاً تكون اللغة بصرية (صناديق وتوصيلات). يمكننا تلخيص هذه النقطة بكلمة واحدة: الإتاحة (accessibility).
- الكود في البريمج عادة يعالج أموراً تفصيلية لا ينصح بمعالجتها في كود اللعبة الأساسي الذي يكون أكثر عموماً. مثلاً، الكود الأساسي قادر على تحريك الشخصيات لكنه غير مسؤول عن تحديد أي الحركات مرتبطة بأية شخصية. البريمج هنا يقوم بتحديد الحركات وطلب تشغيلها وفقاً للأحداث الخارجية. مثال آخر، بريمج يقوم بإظهار سيارة تاكسي تمر في الشارع عند وصول اللاعب لإشارة المرور. لاحظ أن المكونات التي يتعامل معها البريمج خاصة ومحددة.. سيارة تكسي، وإشارة مرور محددة موضوعة في مرحلة بذاتها… مثل هذه التفصيلات يفضل عدم التعامل معها في كود اللعبة الأساسي. إذن لنقل أن البريمجات تدعم التصميم الصحيح من ناحية الهندسة البرمجية (software engineering).
لتطبيق هذا الحل في المشروع، يتم اتباع الآتي:
- بعض الفـِـرَق تقرر تصميم نظام البريمجات بنفسها، فتضع مواصفات لغة برمجة بسيطة جديدة وتبني لها مترجماً أو مفسراً. والبعض الآخر يستخدم حلولاً جاهزة، ولعل أوسع هذه الحلول انتشاراً في الألعاب اليوم هو لغة لوا (LUA).
- إدراج نظام تشغيل البريمجات (script runtime system) ليعمل بجانب الكود الأساسي للعبة والمكتوب بلغة سي أو سي بلس بلس. كود البريمج يتم حفظه في ملفات نصية مستقلة تتم إما ترجمتها (compile) قبل تشغيل اللعبة، أو تفسيرها (interpret) أثناء التشغيل. في بعض الحالات يمكن تعديل البريمجات أثناء التشغيل دون الحاجة لإعادة تشغيل اللعبة (تدعى هذه الميزة التحميل الساخن hot loading).
- يقوم المبرمج بكتابة واجهة تخاطب مع نظام البريمجات كي يستطيع التواصل معه ويتبادل البيانات ويسمح بنداء الإجراءات من كلا الطرفين. كل إجراء وكل متغير يتم تبادله يجب أن يمر ويُعلن عنه في واجهة التخاطب هذه.
نظام البريمجات – نفس النظرة الوردية، لكن الآن مع المزيد من الإضاءة
عندما نقرأ مقالات عن نظام بريمجات في لعبة ما، فإن هذه المقالات في أغلبها تعبر عن فخر كاتبيها بما أنجزوه في اللعبة، وهكذا فهي تسرد محاسن نظام البريمجات وتبشر به كحل ممتاز للمسائل القائمة في اللعبة، لكن محاسن هذه الأنظمة بحد ذاتها تعتبر محط تساؤل وتمحيص. لنسترجع المزايا معاً ونفكر:
- زمن التكرار. أولاً تسريع زمن التكرار ليس هو الحالة العامة. مثلاً، في لغة أنريل سكريبت (UnrealScript) فإن التغيير بأحد ملفات البريمجات قد يستدعي إعادة ترجمة كامل ملفات المشروع من بريمجات وغيرها، مما يستغرق حوالي 45 دقيقة على محطة عمل قوية (هذه المعلومات من خبرة شخصية مباشرة). حسناً أعترف، بصراحة حالة أنريل سكريبت بالذات حالة شاذة تتميز بين أقرانها بالفشل التام، لذلك لن نناقشها الآن. لنعد إلى الحالة العامة… نعم البريمجات تسرع زمن التكرار، لكن لماذا؟ الجواب لأنها صغيرة الحجم ومحدودة المزايا بشكل كبير. فهي تفتقد للمكتبات الواسعة الموجودة في اللغات الكاملة، كما أن البريمج لا يـُـتوقع أن يتجاوز حجماً معيناً.. من خبرتي الشخصية بمجرد أن تبدأ البريمجات بالتوسع قليلاً فإن الزمن اللازم لترجمتها أو تفسيرها يعود ليطفو على السطح ثانية مما يبطئ زمن التكرار.
- الإتاحة. في أغلب الحالات يتم استخدام لغة خاصة للبريمجات تختلف عن أية لغة أخرى على وجه البسيطة. الحجة في ذلك هي تبسيط اللغة لغير المبرمجين.. لكن هذه الحجة بحد ذاتها تؤدي إلى نقطة ضعف هائلة تتمركز في تطوير لغة برمجة جديدة. وهذه النقطة بها عدة نواحي:
- اعتماد لغة جديدة يؤدي إلى تجاهل المهارات الشائعة التي يتم تلقينها في المؤسسات التعليمية المختلفة (جامعات ومعاهد). فأغلب خريجي علوم الحاسوب يتقنون لغات مثل جافا أو سي بلس بلس أو سي شارب. باستخدامنا لغة جديدة فنحن نفرض عليهم تعلم لغة إضافية أخرى.
- إتقان تصميم لغة جديدة ليس بالأمر السهل، لذا فغالباً هذه اللغات تحوي ثغرات تصميمية.
- غياب مكتبات واسعة لإنجاز الحسابات الشائعة كالرياضيات وتركيب النصوص. إضافة هذه المكتبات يستغرق وقتاً كما أنه قد يؤدي إلى تضخم اللغة وإبطاء معالجتها وتفسيرها.
- غياب الأدوات الناضجة لكتابة وتنقيح (debugging) وتتبع الأخطاء في البريمج المكتوب باللغة الجديدة. هذا يفرض على كتاب البريمجات طريقة واحدة فقط في تنقيح الأخطاء، وهي نثر سطور طباعة رسائل هنا وهناك لمعرفة سير البريمج (يدعى هذا الأسلوب printf debugging وهو ممل جداً بالمناسبة).
- الهندسة البرمجية. البرمجيات تفرض الفصل بين الكود الأساسي والكود المخصص للعب فرضاً قاسياً، وهو أمر قد يكون جيداً وقد يكون سيئاً. لا يهم. المهم أن هذا الفصل في الحقيقة لا علاقة له بلغة البرمجة. فهو موضوع تصميمي بحت يمكن فرضه بآليات عديدة أبسط بكثير من استقدام لغة أخرى إلى المشروع. مثلاً، يمكننا حصر الكود الخاص بمنطق اللعبة الخاص في طبقة عليا بعيداً عن البنية التحتية.
الآن دعني أزيدك من المعين ماءً. انظر النقاط التالية:
- سرعة تنفيذ البريمجات بطيئة جداً مقارنة مع اللغات الأساسية المستخدمة في كتابة الألعاب. ففي حالة لغة بريمجات مترجمة (وليست مفسرة) لمستُ سرعة تنفيذ أبطأ بعشرين مرة من سرعة تنفيذ نفس الكود في سي بلس بلس. كود البريمجات يجب أن يكون أصغرياً ومحدوداً، فليس من الحكمة أبداً بناء كامل منطق اللعبة في البريمج، وإلا كان الأداء كارثياً.
- أنظمة تشغيل البريمجات تتسبب بتشتيت الذاكرة بسرعة بسبب أسلوب تعاملها مع الذاكرة (أسلوب تجميع القمامة garbage collection مثلاً). فهي تميل لحجز وتحرير أحجام ضئيلة من الذاكرة وبتكرار عالي وفاعلية منخفضة، وهو ما يمثل كابوساً لنظام إدارة الذاكرة في اللعبة. على الأجهزة محدودة الذاكرة يعتبر هذا التصرف مشكلة حقيقية في البرنامج تهدد سلامته أثناء التشغيل لفترات مستمرة.
- كود الربط بين نظام البريمجات واللغة الأساسية يتطلب جهداً لإحصاء ومتابعة كافة المتغيرات والإجراءات المتبادلة بين الطرفين، كما أنه يعرّض البنية التحتية للتخريب إن أساء البريمج التعامل مع المتغيرات والإجراءات المقدمة له من الكود الأساسي.
الحياة بدون لغة بريمجات
حسناً، إن كانت البريمجات بهذا السوء، فما الحل؟ هل نتفادى استخدامها كلياً؟ أولاً يجب القبول بأن المشاكل المذكورة كزمن التكرار هي مشاكل حقيقية قائمة ويجب حلها. لكن رأيي الشخصي هو أن البريمجات هي حل سيء جداً لهذه المشكلة. هي حل مليء بالتعقيد والسلبيات ويتحول بسهولة لمصدر صداع في الفريق.
لنأخذ مكتبات الربط الديناميكي (dynamic-link libraries) كمثال على حل بديل. كيف يعمل هذا الحل؟
- يتم كشف بعض الوظائف من الكود الأساسي في ملف ترويسة بسيط يحوي كافة الإجراءات والمتغيرات التي نود استخدامها في كتابة منطق اللعبة الخاص. هذه الخطوة بديلة عن كود الربط في نظام البريمجات. هنا الربط تلقائي لأننا ما زلنا نعمل في بيئة اللغة الأصلية للعبة.
- تتم كتابة منطق اللعبة وما إلى ذلك في ملفات كود مستقلة تقوم بتضمين ملف الترويسة آنف الذكر فقط. هذه الملفات تــُبنى في مكتبة ربط ديناميكي تقوم اللعبة بتحميلها واستدعائها أثناء التشغيل.
ماذا حققنا بهذا الحل مقارنة مع البريمجات:
- الفصل بين الكود الأساسي وكود اللعبة.
- الجهد اللازم لبناء واجهة التواصل بين الكود الأساسي وكود اللعبة أصبح أقل وأبسط وأوضح. كتابة ملف ترويسة أبسط بكثير من متابعة واجهة ربط بين لغتين مختلفتين.
- لا حاجة لعمليات تحويل ونسخ بيانات وما إلى ذلك. الإجراءات والمتغيرات كلها متاحة بنفس اللغة الأصلية.
- لا يوجد انحدار في الأداء.
- بيئة التنفيذ تحت السيطرة الكاملة، فعمليات الذاكرة كلها تتم من خلال الواجهة التي نقدمها في ملف الترويسة المذكور.
- زمن التكرار عالي، فترجمة وبناء مكتبة ربط ديناميكي مؤلفة من ملفات صغيرة تتفادى إدراج المكتبات الكبرى هو أمر لا يستغرق أكثر من بضعة ثواني (إن كان جهازك بطيئاً).
- إمكانية التحميل الساخن.
- لغة البرمجة مألوفة وناضجة، ويمكن تبسيطها أكثر عن طريق إزالة الحاجة لاستخدام المزايا المعقدة بها كلياً إن لزم الأمر (مثل حجز وتحرير الذاكرة). قد يجادل البعض أن لغة مثل السي بلس بلس لا يمكن تبسيطها لتصبح متاحة لغير المبرمجين.. لكن لنكن واقعيين هنا، لغات البريمجات القائمة الآن كلها تشبه سي بلس بلس بشكل أو بآخر، فهي تحوي متغيرات وإجراءات وأصناف ووراثة إن لم يكن أكثر وأعقد من ذلك. أنظر إلى لغة لوا LUA مثلاً، هل نزعم أن شخصاً لا يعرف البرمجة قادر على التعامل معها؟
- توفر أدوات الكتابة والتنقيح الناضجة.
حل مكتبات الربط الديناميكي يمكن تحقيقه في العديد من اللغات وعلى عدة منصات، وهو حل بسيط يتفادى إعادة اختراع العجلة، ولا يحتاج لجهد هائل لتطبيقه في مشاريع الألعاب. هكذا تستطيعون الآن معرفة سبب امتعاضي عند سماع خبر عن فريق تطوير ألعاب يقوم بإدراج نظام لغة بريمجات في اللعبة، أو عند التبشير بأن البريمجات هي الحل الصحيح لبناء الألعاب… أعتذر، لكنها ليست الحل الصحيح لمحركات الألعاب… آسف… ابحثوا عن حل آخر…
السلام عليكم ورحمة الله وبركاته…