تنفيذ نظام بيانات اللاعبين وأنظمة الشراء

*This content is translated using AI (Beta) and may contain errors. To view this page in English, click here.

الخلفية

توفر Roblox مجموعة من واجهات برمجة التطبيقات للتفاعل مع مخازن البيانات عبر DataStoreService. حالة الاستخدام الأكثر شيوعًا لهذه الواجهات هي حفظ وتحميل وتكرار بيانات اللاعبين. أي البيانات المرتبطة بتقدم اللاعب، والمشتريات، وغيرها من خصائص الجلسة التي تستمر بين جلسات اللعب الفردية.

تستخدم معظم الألعاب على Roblox هذه الواجهات لتنفيذ شكل من أشكال نظام بيانات اللاعبين. تختلف هذه التطبيقات في نهجها، لكنها بشكل عام تسعى لحل نفس مجموعة المشاكل.

المشاكل الشائعة

فيما يلي بعض من أكثر المشاكل شيوعًا التي تحاول أنظمة بيانات اللاعبين حلها:

  • الوصول في الذاكرة: تتطلب طلبات DataStoreService عمليات طلب ويب تعمل بشكل غير متزامن وخاضعة لحدود معدل معين. هذا مناسب للتحميل الأولي في بداية الجلسة، لكن ليس لعمليات القراءة والكتابة ذات التردد العالي أثناء سير اللعبة. تخزن معظم أنظمة بيانات اللاعبين الخاصة بالمطورين هذه البيانات في الذاكرة على خادم Roblox، مما يحد من طلبات DataStoreService للحالات التالية:

    • القراءة الأولية في بداية الجلسة
    • الكتابة النهائية في نهاية الجلسة
    • الكتابات الدورية على فاصل زمني للتخفيف من السيناريو حيث تفشل الكتابة النهائية
    • الكتابات لضمان حفظ البيانات أثناء معالجة عملية شراء
  • التخزين الفعال: تخزين جميع بيانات جلسة اللاعب في جدول واحد يسمح لك بتحديث قيم متعددة بشكل نووي ومعالجة نفس مقدار البيانات في طلبات أقل. كما أنه يلغي خطر عدم تطابق القيم ويجعل عمليات الاسترداد أسهل.

    كما يقوم بعض المطورين بتنفيذ التسلسل المخصص لضغط هياكل البيانات الكبيرة (عادةً لحفظ المحتوى الذي أنشأه المستخدم داخل اللعبة).

  • التكرار: يحتاج العميل إلى وصول منتظم إلى بيانات اللاعب (على سبيل المثال، لتحديث واجهة المستخدم). يسمح نهج عام لتكرار بيانات اللاعب إلى العميل بنقل هذه المعلومات دون الحاجة إلى إنشاء أنظمة تكرار مخصصة لكل مكون من البيانات. غالبًا ما يرغب المطورون في اختيار ما يتم تكراره وما لا يتم تكراره إلى العميل.

  • معالجة الأخطاء: عندما لا يمكن الوصول إلى مخازن البيانات، ستقوم معظم الحلول بتنفيذ آلية إعادة محاولة ورجوع إلى بيانات "افتراضية". هناك حاجة إلى عناية خاصة لضمان عدم الكتابة اللاحقة للبيانات الافتراضية على البيانات "الحقيقية"، وأن يتم التواصل مع اللاعب بشكل مناسب.

  • إعادة المحاولة: عندما تكون مخازن البيانات غير متاحة، تقوم معظم الحلول بتنفيذ آلية إعادة المحاولة ورجوع إلى بيانات افتراضية. انتبه الخاص لضمان عدم الكتابة اللاحقة للبيانات الافتراضية على البيانات "الحقيقية"، والتواصل بحالة اللاعب بشكل مناسب.

  • قفل الجلسة: إذا تم تحميل بيانات لاعب واحد في الذاكرة على عدة خوادم، يمكن أن تحدث مشاكل حيث يقوم خادم واحد بحفظ معلومات قديمة. يمكن أن يؤدي ذلك إلى فقد البيانات وثغرات تكرار العناصر الشائعة.

  • معالجة عمليات الشراء الذرية: تحقق، وامنح، وسجل المشتريات بشكل ذري لمنع فقدان العناصر أو منحها عدة مرات.

مثال على كود

تقدم Roblox كود مرجعي لمساعدتك في تصميم وبناء أنظمة بيانات اللاعبين. يتناول باقي هذه الصفحة الخلفية، تفاصيل التنفيذ، والتحذيرات العامة.


بعد أن تقوم باستيراد النموذج إلى الاستوديو، يجب أن ترى هيكل المجلدات التالي:

نافذة Explorer تظهر نموذج نظام الشراء.

الهيكل

يوضح هذا المخطط العام الأنظمة الرئيسية في العينة وكيف تتفاعل مع الكود في بقية اللعبة.

مخطط هيكلي لمثال الكود.

إعادة المحاولة

Class: DataStoreWrapper

الخلفية

نظرًا لأن DataStoreService يقوم بعمليات طلب ويب من وراء الكواليس، فإن طلباته لا تضمن النجاح. عندما يحدث ذلك، ترمي طرق DataStore أخطاء، مما يسمح لك بالتعامل معها.

يمكن أن تحدث "مشاكل شائعة" إذا حاولت التعامل مع فشل مخزن البيانات بهذه الطريقة:


local function retrySetAsync(dataStore, key, value)
for _ = 1, MAX_ATTEMPTS do
local success, result = pcall(dataStore.SetAsync, dataStore, key, value)
if success then
break
end
task.wait(TIME_BETWEEN_ATTEMPTS)
end
end

بينما تعتبر هذه آلية إعادة محاولة صالحة تمامًا لوظيفة عامة، إلا أنها ليست مناسبة لطلبات DataStoreService لأنها لا تضمن ترتيب الطلبات التي يتم تنفيذها. الحفاظ على ترتيب الطلبات مهم لطلبات DataStoreService لأنها تتفاعل مع الحالة. اعتبر السيناريو التالي:

  1. يتم إجراء طلب A لتعيين قيمة المفتاح K إلى 1.
  2. يفشل الطلب، لذا يتم جدولة إعادة المحاولة لتعمل بعد 2 ثانية.
  3. قبل حدوث إعادة المحاولة، يقوم الطلب B بتعيين قيمة K إلى 2، لكن إعادة محاولة الطلب A تعيد كتابة هذه القيمة على الفور وتعين K إلى 1.

حتى وإن كانت UpdateAsync تعمل على أحدث إصدار من قيمة المفتاح، إلا أن طلبات UpdateAsync يجب معالجتها في ترتيبها لتجنب الحالات العارضة غير الصحيحة (على سبيل المثال، شراء يخصم العملات قبل معالجة إضافة عملات، مما ينتج عنه عملات سلبية).

يستخدم نظام بيانات اللاعبين لدينا فئة جديدة، DataStoreWrapper، التي توفر إعادة المحاولات التي يتم ضمان معالجتها بالترتيب لكل مفتاح.

النهج

مخطط عملية توضح نظام إعادة المحاولة

يوفر DataStoreWrapper طرقًا تتوافق مع طرق DataStore: DataStore:GetAsync(), DataStore:SetAsync(), DataStore:UpdateAsync() و DataStore:RemoveAsync().

عند استدعاء هذه الطرق:

  1. يتم إضافة الطلب إلى قائمة الانتظار. كل مفتاح له قائمة الانتظار الخاصة به، حيث تتم معالجة الطلبات بالترتيب وفي سلسلة. يتم وضع خيط الطلب في حالة انتظار حتى يكتمل الطلب.

    تستند هذه الوظيفة إلى فئة ThreadQueue، التي هي جدولة مهام بناءً على الكوروتينات ومحدد السرعة. بدلاً من إرجاع وعد، تقوم ThreadQueue بإيقاف الخيط الحالي حتى يكتمل العمل وتطرح خطأ إذا فشل. هذا أكثر توافقًا مع أنماط الأسلوب اللاتزامني في Luau.

  2. إذا فشل الطلب، يتم إعادة المحاولة مع تخلف زمني قابل للتكوين. تشكل هذه المحاولات جزءًا من الاستدعاء المقدم إلى ThreadQueue، لذا يتم ضمان اكتمالها قبل بدء الطلب التالي في قائمة الانتظار لهذا المفتاح.

  3. عند اكتمال الطلب، تعود طريقة الطلب مع نمط success, result.

كما أن DataStoreWrapper يتيح طرقًا للحصول على طول قائمة الانتظار لمفتاح معين وت清。 طرق لاستبعاد الطلبات القديمة. الخيار الأخير مفيد بشكل خاص في السيناريوهات عند إيقاف تشغيل الخادم ولا يوجد وقت لمعالجة أي طلبات باستثناء الطلبات الأحدث.

التحذيرات

يتبع DataStoreWrapper المبدأ القائل بأنه، خارج السيناريوهات القصوى، يجب السماح لكل طلب لمخزن البيانات بالاكتمال (بشكل ناجح أو غير ناجح)، حتى إذا جعل الطلب الأحدث الطلب السابق غير ذي جدوى. عندما يحدث طلب جديد، لا يتم إزالة الطلبات القديمة من قائمة الانتظار، بل يُسمح لها بالاكتمال قبل بدء الطلب الجديد. تستند الأسباب ل ذلك إلى قابلية استخدام هذه الوحدة كأداة عامة لمخازن البيانات بدلاً من أداة محددة لبيانات اللاعبين، وهي كما يلي:

  1. من الصعب تحديد مجموعة من القواعد البديهية متى يمكن إزالة الطلب بأمان من قائمة الانتظار. اعتبر قائمة الانتظار التالية:

    Value=0، SetAsync(1)، GetAsync()، SetAsync(2)

    السلوك المتوقع هو أن GetAsync() ستعيد 1، لكن إذا قمنا بإزالة طلب SetAsync() من قائمة الانتظار بسبب كونه غير ذي جدوى بواسطة الأحدث، سيعيد ذلك 0.

    التقدم المنطقي هو أنه عند إضافة طلب كتابة جديد، يتم تقليم الطلبات القديمة فقط بقدر ما يتعلق الأمر بآخر طلب قراءة حديث. تعتبر UpdateAsync()، أكثر العمليات شيوعًا (وهي الوحيدة التي تستخدمها هذه النظام)، يمكن أن تقرأ وتكتب، لذا سيكون من الصعب التوفيق بين ذلك ضمن هذا التصميم دون إضافة تعقيد إضافي.

    يمكن أن تتطلب DataStoreWrapper منك تحديد ما إذا كانت طلبات UpdateAsync() مسموح لها بالقراءة و/أو الكتابة، لكن لن يكون لذلك جدوى في نظام بيانات اللاعبين لدينا حيث لا يمكن تحديد ذلك مسبقًا بسبب آلية قفل الجلسة (التي تم تناولها بمزيد من التفصيل لاحقًا).

  2. بمجرد إزالتها من قائمة الانتظار، سيكون من الصعب تحديد قاعدة بديهية لكيفية التعامل مع ذلك. عندما يتم إجراء طلب DataStoreWrapper، يتم إيقاف الخيط الحالي حتى يتم الانتهاء منه. إذا قمنا بإزالة الطلبات القديمة من قائمة الانتظار، سيتعين علينا تحديد ما إذا كان ينبغي علينا إرجاع false، "تمت إزالته من قائمة الانتظار" أو عدم إرجاع أي شيء وإهمال الخيط النشط. تتمتع كلتا الطريقتين بعيوبها الخاصة وتحمل تعقيدًا إضافيًا على المستهلك.

في النهاية، رأينا هو أن الطريقة البسيطة (معالجة كل طلب) تفضل هنا وتخلق بيئة أوضح للتنقل في حالات معقدة مثل قفل الجلسة. الاستثناء الوحيد لذلك هو خلال DataModel:BindToClose(), حيث يصبح من الضروري تطهير قائمة الانتظار لحفظ بيانات جميع المستخدمين في الوقت المناسب ولا تكون قيمة الاستدعاءات الخاصة بالفرد مصدر قلق مستمر. لمراعاة هذا، نقوم بفتح طريقة skipAllQueuesToLastEnqueued. لمزيد من السياق، انظر بيانات اللاعبين.

قفل الجلسة

Class: SessionLockedDataStoreWrapper

الخلفية

تُخزن بيانات اللاعب في الذاكرة على الخادم ولا تتم قراءتها أو كتابتها إلى مخازن البيانات الأساسية إلا عند الضرورة. يمكنك قراءة وتحديث بيانات اللاعب المخزنة في الذاكرة على الفور دون الحاجة إلى طلبات ويب وتجنب تجاوز حدود DataStoreService.

لكي يعمل هذا النموذج كما هو مقصود، من الضروري ألا يتمكن أكثر من خادم واحد من تحميل بيانات لاعب في الذاكرة من DataStore في نفس الوقت.

على سبيل المثال، إذا قامت الخادم A بتحميل بيانات لاعب، فلا يمكن للخادم B تحميل تلك البيانات حتى يقوم الخادم A بإصدار قفله عليها خلال عملية الحفظ النهائية. بدون آلية قفل، قد يقوم الخادم B بتحميل بيانات لاعب قديمة من المخزن البيانات قبل أن تتاح للخادم A فرصة حفظ الإصدار الأكثر حداثة الذي لديه في الذاكرة. ثم إذا قام الخادم A بحفظ بياناته الأحدث بعد أن يقوم الخادم B بتحميل البيانات القديمة، سيقوم الخادم B بكتابة تلك البيانات الأحدث خلال حفظه التالي.

على الرغم من أن Roblox يسمح فقط لعميل واحد بالاتصال بخادم واحد في كل مرة، لا يمكنك الافتراض أن البيانات من جلسة واحدة دائمًا محفوظة قبل بدء الجلسة التالية. اعتبر السيناريوهات التالية التي يمكن أن تحدث عندما يغادر اللاعب الخادم A:

  1. يقوم الخادم A بإجراء طلب إلى DataStore لحفظ بياناته، لكن الطلب يفشل ويتطلب العديد من المحاولات لإكماله بنجاح. خلال فترة إعادة المحاولة، ينضم اللاعب إلى الخادم B.
  2. يقوم الخادم A بإجراء الكثير من الاستدعاءات إلى UpdateAsync() إلى نفس المفتاح ويتم تحميله. يتم وضع طلب الحفظ النهائي في قائمة الانتظار. بينما يكون الطلب في قائمة الانتظار، ينضم اللاعب إلى الخادم B.
  3. في الخادم A، يقوم بعض الكود المتصل بحدث PlayerRemoving بإيقاف التنفيذ قبل حفظ بيانات اللاعب. قبل اكتمال هذه العملية، ينضم اللاعب إلى الخادم B.
  4. انخفض أداء الخادم A إلى النقطة التي تأخرت فيها الحفظ النهائي حتى بعد انضمام اللاعب إلى الخادم B.

يجب أن تكون هذه السيناريوهات نادرة، لكنها تحدث، خاصة في الحالات التي يغادر فيها اللاعب خادم A وينتقل إلى خادم آخر بشكل سريع (على سبيل المثال، أثناء الانتقال). قد يحاول بعض المستخدمين الضارين إساءة استخدام هذا السلوك لإكمال الإجراءات دون أن تستمر. يمكن أن يكون لذلك تأثير كبير في الألعاب التي تسمح للاعبين بالتجارة وهو مصدر شائع لاستغلال تكرار العناصر.

يAddressالحفاظ على هذه الثغرة من خلال التأكد من أنه عند القراءة الأولى لمفتاح DataStore للاعب بواسطة الخادم، يكتب الخادم بشكل ذري قفلًا في بيانات المفتاح داخل نفس استدعاء UpdateAsync(). إذا كانت قيمة القفل هذه موجودة عندما يحاول أي خادم آخر قراءة أو كتابة المفتاح، فلا يستمر الخادم.

النهج

مخطط عملية توضح نظام قفل الجلسة

SessionLockedDataStoreWrapper هو غلاف تمويجي حول فئة DataStoreWrapper. يوفر DataStoreWrapper وظائف قائمة الانتظار وإعادة المحاولة، والتي يكملها SessionLockedDataStoreWrapper بإغلاق الجلسة.

يمر SessionLockedDataStoreWrapper كل طلبات DataStore—بغض النظر عما إذا كانت GetAsync، SetAsync أو UpdateAsync—عبر UpdateAsync. وذلك لأن UpdateAsync يسمح بالقراءة والكتابة إلى مفتاح بشكل ذري. كما أنه من الممكن التخلي عن الكتابة بناءً على القيمة المقروءة من خلال إرجاع nil في رد الاستدعاء.

تقوم الدالة التحويلية الممررة إلى UpdateAsync لكل طلب بأداء العمليات التالية:

  1. تتحقق من أن المفتاح آمن للوصول، وتلغي العملية إذا لم يكن كذلك. "آمن للوصول" يعني:

    • لا يحتوي كائن بيانات المفتاح على قيمة LockId غير المعروفة التي تم تحديثها آخر مرة منذ أقل من الوقت المحدد لقفل. هذا يتعلق بالاحترام للقفل المطبق بواسطة خادم آخر وتجاهل ذلك القفل إذا انتهت فترة صلاحيته.

    • إذا كانت هذه الخادم قد وضعت مسبقًا قيمتها LockId في بيانات المفتاح، فلا يزال هذه القيمة موجودة في بيانات المفتاح. هذا يمثل الوضع الذي أخذ فيه خادم آخر القفل الخاص بهذا الخادم (من خلال انتهاء الصلاحية أو بالقوة) وأطلقه لاحقًا. بصياغة بديلة، حتى إذا كانت LockId تساوي nil، قد تكون خادم آخر قد استبدل وأزال قفلًا خلال الوقت منذ أن قمت بقفل المفتاح.

  2. تقوم UpdateAsync بإجراء العملية الخاصة بـ DataStore التي طلبها مستهلك SessionLockedDataStoreWrapper. على سبيل المثال، GetAsync() تترجم إلى function(value) return value end.

  3. استنادًا إلى المعلمات المرسلة إلى الطلب، تقوم UpdateAsync إما بقفل أو فتح المفتاح:

    1. إذا كان سيتم قفل المفتاح، تقوم UpdateAsync بتعيين LockId في بيانات المفتاح إلى GUID. يتم تخزين هذا GUID في الذاكرة على الخادم حتى يمكن التحقق منه في المرة التالية التي يتم فيها الوصول إلى المفتاح. إذا كان الخادم لديه بالفعل قفل على هذا المفتاح، فلا يجري أي تغييرات. يقوم أيضًا بجدولة مهمة لتحذيرك إذا لم تقم بالوصول إلى المفتاح مرة أخرى للحفاظ على القفل ضمن زمن انتهاء القفل.

    2. إذا كان سيتم فتح المفتاح، تقوم UpdateAsync بإزالة LockId في بيانات المفتاح.

يتم تمرير مُعالج إعادة المحاولة مخصص إلى DataStoreWrapper الأساسي بحيث تتم إعادة المحاولة إذا تم إلغاء العملية في الخطوة 1 بسبب قفل الجلسة.

كما يتم إرجاع رسالة خطأ مخصصة إلى المستهلك، مما يسمح لنظام بيانات اللاعبين بالإبلاغ عن خطأ بديل في حالة قفل الجلسة إلى العميل.

التحذيرات

تعتمد آلية قفل الجلسة على أن الخادم يقوم دائمًا بإطلاق قفله على المفتاح عند الانتهاء منه. يجب أن يحدث ذلك دائمًا من خلال تعليمات لفتح المفتاح كجزء من الكتابة النهائية في PlayerRemoving أو BindToClose().

ومع ذلك، يمكن أن يفشل الفتح في بعض الحالات. على سبيل المثال:

  • تعطل الخادم أو كانت DataStoreService غير قابلة للتشغيل لجميع المحاولات للوصول إلى المفتاح.
  • نتيجة لخطأ في المنطق أو خطأ مشابه، لم يتم تحرير تعليمات فتح المفتاح.

للحفاظ على القفل على مفتاح، يجب عليك الوصول إليه بانتظام طالما أنه محمل في الذاكرة. عادةً ما يتم ذلك كجزء من حلقة الحفظ التلقائي التي تعمل في الخلفية في معظم أنظمة بيانات اللاعبين، لكن هذا النظام أيضًا يوفر طريقة refreshLockAsync إذا كنت بحاجة للقيام بذلك يدويًا.

إذا تم تجاوز زمن انتهاء القفل دون تحديث القفل، فإن أي خادم حر في استعادة القفل. إذا أخذ خادم مختلف القفل، فإن محاولات الخادم الحالي لقراءة أو كتابة المفتاح ستفشل ما لم يتمكن من إنشاء قفل جديد.

معالجة المنتجات المطورة

Singleton: ReceiptHandler

الخلفية

تؤدي مكالمة ProcessReceipt الوظيفة الحاسمة المتمثلة في تحديد متى يتم إنهاء عملية الشراء. يتم استدعاء ProcessReceipt في سيناريوهات محددة جدًا. فيما يتعلق بمجموعة ضماناته، انظر MarketplaceService.ProcessReceipt.

على الرغم من أن تعريف "التعامل" مع عملية الشراء يمكن أن يختلف بين الألعاب، فإننا نستخدم المعايير التالية:

  1. لم يتم التعامل مع الشراء مسبقًا.

  2. ينعكس الشراء في الجلسة الحالية.

  3. تم حفظ الشراء في DataStore.

    يجب أن ينعكس كل عملية شراء، حتى المواد الاستهلاكية لمرة واحدة، في DataStore بحيث تكون ضمن سجل مشتريات المستخدمين مع بيانات جلستهم.

يتطلب ذلك إجراء العمليات التالية قبل إرجاع PurchaseGranted:

  1. تحقق من أن PurchaseId لم يتم تسجيله بالفعل على أنه تم التعامل معه.
  2. امنح الشراء في بيانات اللاعب المخزنة في الذاكرة.
  3. سجل PurchaseId كأنه تم التعامل معه في بيانات اللاعب المخزنة في الذاكرة.
  4. اكتب بيانات اللاعب المخزنة في الذاكرة إلى DataStore.

تبسط قفل الجلسة هذه التدفق، حيث لم تعد بحاجة إلى القلق بشأن السيناريوهات التالية:

  • احتمال وجود بيانات اللاعب المخزنة في الذاكرة على الخادم الحالي تكون قديمة، مما يتطلب منك الحصول على أحدث قيمة من DataStore قبل التحقق من سجل PurchaseId.
  • يتطلب الاستدعاء لنفس الشراء تنفيذًا في خادم آخر، مما يتطلب منك القراءة والكتابة وPurchaseId التاريخ وحفظ بيانات اللاعب المحدثة بحيث ينعكس الشراء بشكل ذري لتفادي ظروف السباق.

تضمن قفل الجلسة أنه، إذا كانت المحاولة للكتابة إلى DataStore الناجحة، فلا يوجد خادم آخر قد قرأ بنجاح أو كتب إلى DataStore الخاصة باللاعب بين تحميل البيانات وحفظها في هذا الخادم. باختصار، بيانات اللاعب المخزنة في الذاكرة في هذا الخادم هي أحدث إصدار متوفر. هناك بعض التحذيرات، ولكنها لا تؤثر على هذا السلوك.

النهج

توضح التعليقات في ReceiptProcessor النهج:

  1. تحقق من أن بيانات اللاعب محملة حاليًا على هذا الخادم وأنها حملت دون أي أخطاء.

    نظرًا لأن هذا النظام يستخدم قفل الجلسة، فإن هذه الفحص يتحقق أيضًا من أن البيانات المخزنة في الذاكرة هي أحدث إصدار متوفر.

    إذا لم تُحمل بيانات اللاعب بعد (وهو أمر متوقع عندما ينضم اللاعب إلى اللعبة)، انتظر لتحميل بيانات اللاعب. يستمع النظام أيضًا لرحيل اللاعب من اللعبة قبل تحميل بياناته، حيث ينبغي ألا ينتظر بلا نهاية ويعيق استدعاء هذا لحدث مرة أخرى على هذا الخادم لهذه العملية إذا انضم اللاعب مرة أخرى.

  2. تحقق من أن PurchaseId لم يتم تسجيله كمعالج بالفعل في بيانات اللاعب.

    بسبب قفل الجلسة، فإن مصفوفة PurchaseIds التي يحتويها النظام في الذاكرة هي أحدث إصدار متاح. إذا كانت PurchaseId مسجلة على أنها معالج وتنعكس في قيمة تم تحميلها أو حفظها في DataStore، ارجع PurchaseGranted. إذا كانت مسجلة كمعالج، ولكن لا تنعكس في DataStore، ارجع NotProcessedYet.

  3. تحديث بيانات اللاعب محليًا في هذا الخادم "لمنح" الشراء.

    يتبنى ReceiptProcessor أسلوب دعوة عام وينسب دعوة مختلفة لكل DeveloperProductId.

  4. تحديث بيانات اللاعب محليًا في هذا الخادم لتخزين PurchaseId.

  5. تقديم طلب لحفظ البيانات المخزنة في الذاكرة إلى DataStore، مع إرجاع PurchaseGranted إذا كانت الطلب ناجحًا. إذا لم يكن الأمر كذلك، ارجع NotProcessedYet.

    إذا لم يكن هذا الطلب للحفظ ناجحًا، قد يظل الطلب اللاحق لحفظ بيانات الجلسة المخزنة للاعب ناجحًا. خلال استدعاء ProcessReceipt التالي، يتعامل الخطوة 2 مع هذه الحالة ويعيد PurchaseGranted.

بيانات اللاعب

Singletons: PlayerData.Server، PlayerData.Client

الخلفية

تعد الوحدات التي توفر واجهة لقراءة وكتابة بيانات جلسة اللاعب بشكل متزامن شائعة في ألعاب Roblox. يتناول هذا القسم PlayerData.Server وPlayerData.Client.

النهج

يتعامل PlayerData.Server وPlayerData.Client مع ما يلي:

  1. تحميل بيانات اللاعب إلى الذاكرة، بما في ذلك التعامل مع الحالات التي تفشل فيها عملية التحميل
  2. توفير واجهة للكود الخادم لاستعلام وتغيير بيانات اللاعب
  3. تكرار التغييرات في بيانات اللاعب إلى العميل بحيث يمكن للكود العميل الوصول إليها
  4. تكرار أخطاء التحميل و/أو الحفظ إلى العميل بحيث يمكنه عرض محادثات الخطأ
  5. حفظ بيانات اللاعب بشكل دوري، عندما يغادر اللاعب، وعندما يتم إيقاف تشغيل الخادم

تحميل بيانات اللاعب

مخطط عملية يوضح نظام التحميل
  1. يقوم SessionLockedDataStoreWrapper بإجراء طلب getAsync إلى مخزن البيانات.

    إذا فشل هذا الطلب، يتم استخدام البيانات الافتراضية ويتم وضع العلامة على الملف الشخصي كـ "خطأ" لضمان عدم الكتابة إلى مخزن البيانات لاحقًا.

    خيار بديل هو طرد اللاعب، لكننا نوصي بالسماح للاعب باللعب باستخدام البيانات الافتراضية مع رسائل واضحة حول ما حدث بدلاً من إزالته من اللعبة.

  2. يتم إرسال حمولة أولية إلى PlayerDataClient تحتوي على البيانات التي تم تحميلها وحالة الخطأ (إن وجدت).

  3. أي خيوط مُعلقة تستخدم waitForDataLoadAsync من أجل اللاعب يتم استئنافها.

توفير واجهة لكود الخادم

  • يُعتبر PlayerDataServer كائنًا مفردًا يمكن استدعاؤه والوصول إليه من قِبل أي كود خادم يعمل في نفس البيئة.
  • يتم تنظيم بيانات اللاعب في قاموس من المفاتيح والقيم. يمكنك التلاعب بهذه القيم على الخادم باستخدام طرق setValue وgetValue وupdateValue وremoveValue. تعمل جميع هذه الطرق بشكل متزامن دون أي توقف.
  • تتوفر طرق hasLoaded وwaitForDataLoadAsync لضمان تحميل البيانات قبل الوصول إليها. نوصي بالقيام بذلك مرة واحدة خلال شاشة التحميل قبل بدء الأنظمة الأخرى لتفادي الحاجة إلى التحقق من أخطاء التحميل قبل كل تفاعل مع البيانات على العميل.
  • يمكن لأسلوب hasErrored الاستعلام عمّا إذا كانت عملية التحميل الأولية قد فشلت، مما يتسبب في استخدام بيانات افتراضية. تحقق من هذه الطريقة قبل السماح للاعب بإجراء أي عمليات شراء، حيث لا يمكن حفظ المشتريات في البيانات دون تحميل ناجح.
  • يتم إطلاق إشارة playerDataUpdated مع player وkey وvalue في كل مرة يتم فيها تغيير بيانات لاعب. يمكن للأنظمة الفردية الاشتراك في هذا.

تكرار التغييرات إلى العميل

  • يتم تكرار أي تغيير في بيانات اللاعب في PlayerDataServer إلى PlayerDataClient، ما لم يتم وضع علامة على المفتاح كخاص باستخدام setValueAsPrivate.
    • تُستخدم setValueAsPrivate للدلالة على المفاتيح التي لا ينبغي إرسالها إلى العميل.
  • يتضمن PlayerDataClient طريقة للحصول على قيمة مفتاح (get) وإشارة تُطلق عند تحديثها (updated). تتوفر أيضًا طريقة hasLoaded وإشارة loaded، بحيث يمكن للعميل الانتظار لتحميل البيانات وتكرارها قبل بدء أنظمته.
  • يُعتبر PlayerDataClient كائنًا مفردًا يمكن استدعاؤه والوصول إليه من قِبل أي كود عميل يعمل في نفس البيئة.

تكرار الأخطاء إلى العميل

  • يتم تكرار حالات الخطأ التي تظهر عند حفظ أو تحميل بيانات اللاعب إلى PlayerDataClient.
  • يمكن الوصول إلى هذه المعلومات باستخدام طرق getLoadError وgetSaveError، بالإضافة إلى الإشارات loaded وsaved.
  • هناك نوعان من الأخطاء: DataStoreError (فشل طلب DataStoreService) وSessionLocked (انظر قفل الجلسة).
  • استخدم هذه الأحداث لتعطيل مطالبات شراء العميل وتنفيذ محادثات التحذير. توضح هذه الصورة مثالًا على محادثة التحذير:
لقطة شاشة لمثال على تحذير قد يتم عرضه عندما تفشل بيانات اللاعب في التحميل

حفظ بيانات اللاعب

مخطط عملية توضح نظام الحفظ
  1. عند مغادرة اللاعب اللعبة، يقوم النظام بالخطوات التالية:

    1. تحقق مما إذا كان من الآمن كتابة بيانات اللاعب في مخزن البيانات. تشمل السيناريوهات حيث قد يكون ذلك غير آمن فشل تحميل بيانات اللاعب أو لا تزال قيد التحميل.
    2. قم بإجراء طلب من خلال SessionLockedDataStoreWrapper لكتابة قيمة البيانات الحالية في الذاكرة إلى مخزن البيانات وإزالة القفل الجلسة بمجرد الانتهاء.
    3. امسح بيانات اللاعب (وغيرها من المتغيرات مثل البيانات الوصفية وحالات الخطأ) من ذاكرة الخادم.
  2. في حلقة دورية، يكتب الخادم بيانات كل لاعب إلى مخزن البيانات (شرط أن يكون من الآمن الحفظ). يعمل هذا التكرار الترحيبي على تخفيف الخسارة في حالة تعطل الخادم وللحفاظ على قفل الجلسة.

  3. عند تلقي طلب بإيقاف تشغيل الخادم، يحدث ما يلي في ردِّ BindToClose:

    1. يتم إرسال طلب لحفظ بيانات كل لاعب في الخادم، اتباعًا للإجراءات المعتادة عند مغادرة اللاعب للخادم. يتم تقديم هذه الطلبات بشكل متوازي، حيث أن ردود BindToClose لا تستغرق أكثر من 30 ثانية لإكمالها.
    2. لتسريع الحفظ، تتم إزالة جميع الطلبات الأخرى في قائمة انتظار كل مفتاح من DataStoreWrapper الأساسية (انظر إعادة المحاولة).
    3. لا يُرجع الرد حتى تكتمل جميع الطلبات.
©2026 شركة Roblox Corporation. تُعد منصّة Roblox، وشعار Roblox وشعار "توسيع حدود المخيلة"، من ضمن علاماتنا التجارية المسجّلة وغير المسجّلة في الولايات المتحدة وبلدان أخرى.