مقدمة: بادئ ذي بدء ، سأتحدث عن المشروع حتى تكون هناك أفكار حول كيفية عملنا في المشروع وإعادة خلق الألم الذي شعرنا به.
كمطور ، دخلت المشروع في 2015-2016 ، لا أتذكر بالضبط ، لكنه كان يعمل قبل 2-3 سنوات. حظي المشروع بشعبية كبيرة في مجاله ، أي خوادم الألعاب. كم هو غريب ، لم يكن الأمر كذلك ، لكن المشاريع على خوادم الألعاب مستمرة حتى يومنا هذا ، لقد رأيت في الآونة الأخيرة وظائف شاغرة وعملت قليلاً في نفس الفريق. نظرًا لأن خوادم اللعبة مبنية على لعبة تم إنشاؤها بالفعل ، لذلك ، يتم استخدام لغة البرمجة النصية لتطوير مضمن في محرك اللعبة.
نحن نعمل على تطوير ما يقرب من الصفر في مشروع Garry's Mod (Gmod) ، من المهم أن نلاحظ أنه في وقت كتابة هذا التقرير ، كان هاري بالفعل ينشئ مشروع S & Box جديد على Unreal Engine. نحن لا نزال نجلس على المصدر.
وهو عموما غير مناسب لموضوع الخادم الخاص بنا.

"ما هي قصتك مخيفة؟" - أنت تسأل.
لدينا سمة قوية لخادم اللعبة ، وهي "Stalker" وحتى مع عناصر ألعاب لعب الأدوار (RP) ، يثور السؤال على الفور - "كيف يمكن للجميع تنفيذ ذلك على خادم واحد؟".
نظرًا لأن محرك Source قديم (يتم استخدام إصدار 2013 أيضًا في Gmod 32 bit) ، فلن تصنع بطاقات كبيرة ، وهناك قيود صغيرة على عدد Entity و Mesh وغير ذلك الكثير.
الذي عمل على المحرك سوف يفهم.
اتضح أن المهمة مستحيلة عمومًا ، لجعل مطارد متعدد اللاعبين نظيفًا مع المهام وعناصر آر بي جي من الأصل نفسه ويفضل أن يكون مؤامرة صغيرة.
بادئ ذي بدء ، كان الإملاء الأولي صعبًا (تم كتابة العديد من الإجراءات من الفئة: رمي كائن ، ورفع كائن من نقطة الصفر) ، على أمل أن يكون من الأسهل الاستمرار ، لكن المتطلبات زادت. كانت آليات اللعبة جاهزة ، وكان كل ما تبقى هو القيام بالذكاء والترقية وجميع أنواع الأشياء. بشكل عام ، نقل جميع ما في وسعهم.

بدأت المشاكل بالفعل أثناء عمل الإصدار الأول للإصدار ، وهي (التأخيرات ، تأخيرات الخادم).
يبدو أن خادمًا قويًا يمكنه معالجة الطلبات بهدوء واستيعاب Gamemode بالكامل.
وصف بسيط لل gamemodeهذا هو اسم مجموعة من البرامج النصية المكتوبة لوصف آليات الخادم نفسه
على سبيل المثال: نريد موضوع "المعارك الملكية" الشهيرة الآن ، مما يعني أن الاسم يجب أن يتوافق مع آليات اللعبة أيضًا. "تفرخ اللاعبين على متن الطائرة ، يمكنك التقاط الأشياء ، يمكن للاعبين التواصل ، لا يمكنك ارتداء أكثر من خوذة ، إلخ." - كل هذا موصوف من قبل ميكانيكا اللعبة على الخادم.
كانت التخلفات على جانب الخادم بسبب العدد الكبير من اللاعبين ، حيث إن أحد اللاعبين يستهلك الكثير من ذاكرة الوصول العشوائي (RAM) بحوالي 80-120 ميغابايت (لا يتم حساب العناصر الموجودة في المخزون ، والمهارات ، وما إلى ذلك) ، ومن ناحية العميل ، كان هناك انخفاض قوي FPS
لم تكن طاقة وحدة المعالجة المركزية كافية لمعالجة الفيزياء ، بل كان من الضروري استخدام كائنات ذات خصائص مادية أقل.
وكذلك بالإضافة إلى ذلك ، كانت نصوصنا المكتوبة ذاتيا والتي لم يتم تحسينها على الإطلاق.

أولاً ، بالطبع ، نقرأ مقالات عن التحسين في لوا. حتى في حالة
الانتحار ، أرادوا كتابة DLL في C ++ ، لكن المشكلة نشأت في تنزيل DLL من الخادم إلى العملاء. باستخدام C ++ لـ DLL ، يمكنك كتابة برنامج يقوم باعتراض البيانات بهدوء ؛ أضاف مطورو Gmod امتدادًا للاستثناءات لتنزيلها من قِبل العملاء (الأمان ، على الرغم من أنه لم يحدث بالفعل). على الرغم من أنها ستكون مريحة وسيصبح Gmod أكثر مرونة ، ولكن أكثر خطورة.
بعد ذلك ، نظرنا إلى الملف التعريفي (لحسن الحظ ، كتبه الأشخاص الأذكياء) وكان هناك رعب في الوظائف ، ولوحظ أنه في البداية كانت هناك وظائف بطيئة جدًا في مكتبة محركات Gmod.
إذا حاولت الكتابة في Gmod ، فأنت تدرك جيدًا أن هناك مكتبة مدمجة تسمى الرياضيات.
وأبطأ وظائف في ذلك بالطبع math.Clamp والرياضيات.
من خلال استعراض رمز الأشخاص ، لوحظ أن الوظائف قد ألقيت في اتجاهات مختلفة ، يتم استخدامها في كل مكان تقريبًا ، ولكن بشكل غير صحيح!
هيا بنا إلى ممارسة. على سبيل المثال ، نريد تقريب إحداثيات متجه الموضع لنقل الكيان (على سبيل المثال ، لاعب).
local x = 12.5 local y = 14.9122133 local z = 12.111 LocalPlayer():SetPos( Vector( Math.Round(x), Math.Round(y), Math.Round(z) )
3 وظائف التقريب معقدة ، ولكن لا شيء خطير ، ما لم يكن بالطبع في حلقة وليس استخدامها في كثير من الأحيان ، ولكن المشبك هو أصعب.
غالبًا ما يتم استخدام الكود التالي في المشاريع ولا يريد أحد تغيير أي شيء.
self:setLocalVar("hunger", math.Clamp(current + 1, 0, 100))
على سبيل المثال ، يشير المصير إلى كائن اللاعب ولديه متغير محلي اخترعناه ، والذي عند إعادة التعيين إلى الخادم تتم إعادة تعيينه ، math.Clamp عندما تقوم الحلقة بإجراء مهمة سلسة ، فإنها ترغب في إنشاء واجهة سلسة على Clamp.
تنشأ المشاكل عندما تعمل على كل لاعب يزور الخادم. حالة نادرة ، ولكن إذا كان الإصدار 5-15 (يعتمد فورًا على تكوين الخادم) يدخل الخادم في وقت واحد وتبدأ هذه الوظيفة الصغيرة والبسيطة في العمل للجميع ، فسيكون للخادم تأخير جيد في وحدة المعالجة المركزية. والأسوأ من ذلك إذا كان math.Clamp في حلقة.
التحسين في الواقع بسيط للغاية ، فأنت تقوم بترجمة وظائف الحمل بكثرة. يبدو بدائيًا ، لكن في 3 gamemode والعديد من الوظائف الإضافية رأيت هذا الرمز البطيء.
إذا كنت بحاجة إلى الحصول على القيمة واستخدامها في المستقبل ، فلن تحتاج إلى الحصول عليها مرة أخرى إذا لم تتغير. بعد كل شيء ، سيتلقى اللاعب الذي يدخل الخادم في أي حال جوعًا يساوي 100 ، لذلك يكون هذا الرمز أسرع عدة مرات.
local value = math.Clamp(current + 1, 0, 100) self:setLocalVar("hunger", value)
كل شيء على ما يرام ، بدأوا في البحث عن كيفية عمله. نتيجة لذلك ، بدأنا في هوس لتحسين كل شيء.
لاحظنا أن معيار الحلقة بطيء ، وقررنا الخروج بالدراجة الخاصة بنا والتي ستكون أسرع (لم ننسى لعبة البلاك جاك) ثم بدأت اللعبة.

المفسدلقد نجحنا في إنشاء أسرع حلقة على Lua Gmod ، ولكن بشرط أن يكون هناك أكثر من 100 عنصر.
انطلاقًا من الوقت الذي تمضيه في دورتنا واستخدامها في الكود ، حاولنا دون جدوى القيام بذلك لأنه وجد تطبيقًا فقط في البويضات على خريطة الحالات الشاذة بعد إزالتها وإزالتها.
وهكذا إلى الرمز. على سبيل المثال ، تحتاج إلى العثور على جميع الكيانات مع اسم في بداية الشذوذ ، لدينا الشذوذ في اسم هذا الفصل.
فيما يلي النص العادي على Lua Gmod:
local anomtable = ents.FindByClass("anom_*") for k, v in pairs(anomtable) do v:Remove() end
هنا للمدخن:
من الواضح على الفور أن رمز
g * الواضح سيكون أبطأ من المعيار "بالنسبة للأزواج" ، ولكن كما اتضح.
local b, key = ents.FindByClass("anom_*"), nil repeat key = next(b, key) b[key]:Remove() until key != nil
لإجراء تحليل كامل لخيارات الحلقة هذه ، تحتاج إلى ترجمتها إلى برنامج نصي Lua منتظم.
على سبيل المثال ، سيكون anomtable 5 عناصر.
يتم استبدال الإزالة بالإضافة المعتادة. الشيء الرئيسي هو معرفة الفرق في عدد الإرشادات بين الخيارين لتنفيذ الحلقة.
دورة الفانيلا:
local anomtable = { 1, 2, 3, 4, 5 } for k, v in pairs(anomtable) do v = v + 1 end
لدينا شيء عظيم:
local b, key = { 1, 2, 3, 4, 5 }, nil repeat key = next(b, key) b[key] = b[key] + 1 until key ~= nil
دعونا نلقي نظرة على رمز المترجم الشفهي (
مثل المجمع ، لا ينصح أن ننظر تحت المفسد كمبرمج عالي المستوى ).
فقط في حالة ، إزالة جونز من الشاشات. لقد حذرت.دورة الفانيليا ; Name: for1.lua ; Defined at line: 0 ; #Upvalues: 0 ; #Parameters: 0 ; Is_vararg: 2 ; Max Stack Size: 7 1 [-]: NEWTABLE R0 5 0 ; R0 := {} 2 [-]: LOADK R1 K0 ; R1 := 1 3 [-]: LOADK R2 K1 ; R2 := 2 4 [-]: LOADK R3 K2 ; R3 := 3 5 [-]: LOADK R4 K3 ; R4 := 4 6 [-]: LOADK R5 K4 ; R5 := 5 7 [-]: SETLIST R0 5 1 ; R0[(1-1)*FPF+i] := R(0+i), 1 <= i <= 5 8 [-]: GETGLOBAL R1 K5 ; R1 := pairs 9 [-]: MOVE R2 R0 ; R2 := R0 10 [-]: CALL R1 2 4 ; R1,R2,R3 := R1(R2) 11 [-]: JMP 13 ; PC := 13 12 [-]: ADD R5 R5 K0 ; R5 := R5 + 1 13 [-]: TFORLOOP R1 2 ; R4,R5 := R1(R2,R3); if R4 ~= nil then begin PC = 12; R3 := R4 end 14 [-]: JMP 12 ; PC := 12 15 [-]: RETURN R0 1 ; return
دورة دراجة تفكيك ; Name: for2.lua ; Defined at line: 0 ; #Upvalues: 0 ; #Parameters: 0 ; Is_vararg: 2 ; Max Stack Size: 6 1 [-]: NEWTABLE R0 5 0 ; R0 := {} 2 [-]: LOADK R1 K0 ; R1 := 1 3 [-]: LOADK R2 K1 ; R2 := 2 4 [-]: LOADK R3 K2 ; R3 := 3 5 [-]: LOADK R4 K3 ; R4 := 4 6 [-]: LOADK R5 K4 ; R5 := 5 7 [-]: SETLIST R0 5 1 ; R0[(1-1)*FPF+i] := R(0+i), 1 <= i <= 5 8 [-]: LOADNIL R1 R1 ; R1 := nil 9 [-]: GETGLOBAL R2 K5 ; R2 := next 10 [-]: MOVE R3 R0 ; R3 := R0 11 [-]: MOVE R4 R1 ; R4 := R1 12 [-]: CALL R2 3 2 ; R2 := R2(R3,R4) 13 [-]: MOVE R1 R2 ; R1 := R2 14 [-]: GETTABLE R2 R0 R1 ; R2 := R0[R1] 15 [-]: ADD R2 R2 K0 ; R2 := R2 + 1 16 [-]: SETTABLE R0 R1 R2 ; R0[R1] := R2 17 [-]: EQ 1 R1 K6 ; if R1 == nil then PC := 9 18 [-]: JMP 9 ; PC := 9 19 [-]: RETURN R0 1 ; return
سيقول الشخص قليل الخبرة ببساطة أن الدورة العادية أسرع لأن هناك عددًا أقل من التعليمات (15 مقابل 19).
لكن يجب ألا ننسى أن كل تعليمات في المترجم الشفهي بها دورات للمعالج.
انطلاقًا من كود فك الشفرة في الدورة الأولى ، هناك تعليمة forloop مكتوبة مسبقًا للعمل مع المصفوفة ، يتم تحميل المصفوفة في الذاكرة ، وتصبح عالمية ، ونقفز فوق العناصر ونضيف ثابتًا.
في الخيار الثاني ، تختلف الطريقة ، والتي تعتمد بشكل أكبر على الذاكرة ، وتتلقى جدولًا ، وتغير عنصرًا ، وتضبط جدولًا ، وتتحقق من الصفر ، وتدعو مرة أخرى.
دورتنا الثانية سريعة لأن هناك الكثير من الشروط والإجراءات في تعليمة واحدة (R4 ، R5: = R1 (R2 ، R3) ؛ إذا كان R4 ~ = لا شيء ، فابدأ الكمبيوتر = 12 ؛ R3: = R4 النهاية) بسبب هذا إنه يأكل كثيرًا
، ويأخذ دورات ساعة وحدة المعالجة المركزية للتنفيذ ، والماضي مرتبط مرة أخرى بالذاكرة.
التعليمات forloop مع عدد كبير من العناصر يستسلم لدورتنا على سرعة مرور جميع العناصر. هذا يرجع إلى حقيقة أن معالجة العنوان مباشرة أسرع ، أقل من أي الأشياء الجيدة من أزواج. (وليس لدينا إنكار)
بشكل عام ، في الخفاء ، أي استخدام للنفي في الكود يبطئه ؛ لقد تم اختباره بالفعل عن طريق الاختبارات والوقت. سيعمل المنطق السلبي بشكل أبطأ لأن المعالج ALU لديه وحدة حوسبة منفصلة "العاكس" ، للمعامل الأحادي (لا ،!) للعمل ، ستحتاج إلى الوصول إلى العاكس وهذا سيستغرق وقتًا إضافيًا.
خاتمة: ليس كل شيء أفضل دائمًا ، فدراجاتك يمكن أن تكون مفيدة ، لكن مرة أخرى في مشروع حقيقي ، يجب ألا تتوصل إليها إذا كانت سرعة الإطلاق مهمة بالنسبة لك. نتيجة لذلك ، أكملنا التطوير الكامل من عام 2014 إلى يومنا هذا ، وهو نوع من "الانتظار". على الرغم من أنه يبدو وكأنه خادم ألعاب منتظم يتم تثبيته في يوم واحد ويتم تهيئته بالكامل للعبة في يومين ، إلا أنه يجب أن تكون قادرًا على تقديم شيء جديد.
لا يزال هذا المشروع طويل الأجل يشهد الإصدار الثاني من نفسه حيث يوجد الكثير من التحسينات في الكود ، لكنني سأتحدث عن تحسينات أخرى في المقالات التالية. دعم بالنقد أو التعليق ، صحيح إذا كنت مخطئًا.