التطوير / ROBLOX
UpdateAsync في Roblox: لماذا قد تفقد عمليتا حفظ تغييراً واحداً
يقرأ خادمان خياليان لورشة القيمة 100 ويضيف كل منهما 5، لكن النتيجة المحفوظة تبقى 105. نفصل استبدال نسخة قديمة عن تحويل الحالة الحالية، ونرسم تسلسل التعارض، ونختبر دالة محلية دون الاتصال بمخزن حقيقي.
افحص التسلسل قبل اتهام زر الحفظ #
تخيل عداداً مشتركاً للتوصيلات المكتملة في تمرين تعليمي. يجب أن يضيف معالجان مستقلان خمس وحدات لكل منهما. يعلن كلاهما انتهاء عمله، لكن الإجمالي يرتفع مرة واحدة فقط. قد يحدث ذلك عند حساب المقترحين من النسخة القديمة نفسها. هذا العرض وحده لا يثبت السبب في لعبتك؛ سجل الأفعال وشروط الكتابة وأساس الحساب أولاً.
يتابع هذا الدليل موضوع الحفظ الأول، لكنه يعالج سؤالاً مختلفاً: تعتمد القيمة الجديدة على حالة قد يغيرها خادم آخر. نستخدم عداداً تعليمياً معزولاً، لا محفظة لاعب ولا سجل شراء ولا ملفاً كاملاً. الأعداد والترتيبات هنا خيالية. لم نشغل خادمين حقيقيين في Roblox لإنتاج هذه الأمثلة.
ارسم تسلسل الكتابة من نسخة قديمة #
اكتب خمسة أسطر: يحتوي المخزن على 100؛ يقرأ المعالج A القيمة 100؛ يقرأ B القيمة 100؛ يكتب A القيمة 105؛ يكتب B مقترحه الخاص 105. لا يحتاج B إلى نية ضارة لفقدان زيادة A. إنه يستبدل السجل بحساب أُعد قبل الكتابة الأولى. يفترض أن تعطي زيادتان القيمة 110، بينما يترك استبدالان متساويان القيمة 105.
احتفظ بالرسم بجوار المهمة. فهو يميز معالجاً لم يعمل، واستدعاء شبكة فاشلاً، وكتابات ناجحة مبنية على أساس قديم. في التمرين المحلي يكفي اسم العملية ومعرف طلب خيالي والقيمة المدخلة والنتيجة المقترحة. لا تحتاج إلى معلومات خاصة باللاعب أو بيانات دخول لتوضيح المشكلة.
| الخطوة | الفعل | القيمة المحفوظة |
|---|---|---|
| 1 | البداية | 100 |
| 2 | يقرأ A القيمة 100 | 100 |
| 3 | يقرأ B القيمة 100 | 100 |
| 4 | يكتب A القيمة 105 | 105 |
| 5 | يكتب B القيمة 105 | 105 |
الاستبدال والتحويل يعبران عن قصدين مختلفين #
تضع SetAsync قيمة المفتاح دون قراءته أولاً ضمن هذه الطريقة. يناسب ذلك استبدالاً بقيمة معروفة لا تعتمد على الحالة السابقة. أما إضافة خمس إلى العداد الحالي فتتطلب الانتباه إلى أساس الحساب. توصي Roblox باستخدام UpdateAsync عندما تعتمد الكتابة على القيمة الحالية أو يمكن لعدة خوادم كتابة المفتاح نفسه.
تغيير اسم الطريقة فقط مع إعادة 105 المحسوبة مسبقاً من callback لا يصلح التسلسل. لا يزال المقترح مبنياً على النسخة القديمة. احسب من الوسيط current الذي يصل إلى callback. اشرح الفرق قبل نقله إلى جدول مخزون: تحويل حالة حديثة ليس إعادة إرسال لقطة قديمة تخصك.
قد يُستدعى callback مجدداً #
إذا تغير المفتاح بواسطة خادم آخر بين القراءة والكتابة، تستطيع UpdateAsync تجاهل المقترح السابق واستدعاء التحويل مرة أخرى. لذلك لا يعني استدعاء الطريقة مرة واحدة تنفيذ callback مرة واحدة. في ترتيبنا الخيالي يقترح A القيمة 105، ويحفظ B القيمة 105 أولاً، ثم يعيد A الحساب من 105 ليقترح 110.
يجب ألا يمنح التحويل المتكرر عنصراً آخر، أو يرسل حدث مكافأة، أو يغير كائناً خارجياً في اللعبة. وظيفته حساب السجل المقترح. تتطلب نتائج اللعب قراراً منفصلاً يعتمد على حالة مؤكدة. نقل منح العنصر إلى ما بعد نجاح الاستدعاء لا يثبت وحده أن معالجاً آخر لا يستطيع طلب العملية التجارية نفسها مجدداً.
دالة تعليمية دون حالة خفية #
يوفر ModuleScript باسم CounterTransform الدالة Add(current, delta). تبدأ من صفر للسجل غير الموجود، وتقبل قيماً صحيحة غير سالبة وزيادة صحيحة موجبة، وتحد النتيجة بمليون. هذه قواعد اختيرت للعداد التعليمي. قد تختلف البداية والمدى المسموح في لعبة أخرى؛ ليست قواعد عامة لملفات اللاعبين.
تعيد الدالة عدداً جديداً ولا تغير المتغيرات الخارجية. تنتج المدخلات المتساوية نتائج متساوية. يعيد النوع الخاطئ أو الزيادة السالبة أو الكسرية أو تجاوز الحد القيمة nil. لا تحتوي الدالة على شبكة أو انتظار أو منح مكافأة أو وصول إلى اللاعب. نفذنا اختبارات Luau محلية لها، لكنها لا تثبت سلوك مخزن Roblox الحقيقي.
-- Pure transform for an isolated teaching counter, not a player profile.
local Transform = {}
local LIMIT = 1000000
local function validInteger(value)
return type(value) == "number" and value == math.floor(value)
and value >= 0 and value <= LIMIT
end
function Transform.Add(current, delta)
if not validInteger(delta) or delta == 0 then return nil end
if current == nil then current = 0 end
if not validInteger(current) then return nil end
if delta > LIMIT - current then return nil end
return current + delta
end
return Transform
اربط التحويل في تجربة اختبار معزولة #
ضع CounterTransform في ServerScriptService داخل experience اختبار منفصلة. يحصل Script الخادم على DataStoreService ويختار مخزن العداد التعليمي ويحمل الوحدة عبر require. مرر إلى UpdateAsync دالة callback تعيد CounterTransform.Add(current, 5). لا تضع داخل التحويل task.wait أو تحميل مورد أو عملية أخرى توقف التنفيذ مؤقتاً.
استدعاء UpdateAsync نفسه عملية شبكة. ضعه داخل pcall وافحص القيمة المعادة بصورة منفصلة. نجاح pcall يعني عدم التقاط خطأ؛ إذا ألغى callback الكتابة عبر nil فذلك لا يؤكد حفظ زيادة خمس. لا يعزل اسم يحتوي كلمة «اختبار» المخزن تلقائياً إذا كانت experience والمفاتيح ما زالت تستخدم بيانات الإنتاج.
-- Server Script in an isolated test experience only.
-- This demonstrates one numeric teaching key, not a player profile.
local DataStoreService = game:GetService("DataStoreService")
local CounterTransform = require(
game:GetService("ServerScriptService"):WaitForChild("CounterTransform")
)
local store = DataStoreService:GetDataStore("GuidebookCounterExercise_v1")
local ok, result = pcall(function()
return store:UpdateAsync("CounterExercise", function(current)
return CounterTransform.Add(current, 5)
end)
end)
if not ok then
warn("Write response failed; outcome is not established", result)
elseif result == nil then
warn("Update cancelled; no new saved counter was returned")
else
print("Updated teaching counter", result)
end
الإلغاء لا يعني تصفير السجل سراً #
إعادة nil من callback تلغي التحديث. يفيد ذلك في التمرين عند وجود نوع غير متوقع أو تجاوز النتيجة الحد. لا تحول سلسلة تالفة إلى صفر لمجرد متابعة المسار. قد تخفي مشكلة بيانات وتستبدل سجلاً يحتاج إلى تحقيق. غياب السجل ووجود سجل غير صالح حالتان مختلفتان.
للتشخيص سجل عدم الحصول على العدد المحفوظ المتوقع وأوقف الخطوات التالية لهذا المسار التعليمي. لا تقدم الدالة الصغيرة أسباب رفض مفصلة. إذا احتاج مشروعك إلى سجل أسباب، فصممه بحيث لا يتحول تكرار callback إلى فعل لعب إضافي ولا يغير أساس الحساب اللاحق.
اختبر النموذج والدالة كلّاً على حدة #
أعد أولاً الترتيب الخاطئ: نسختان من 100، ومقترحان بقيمة 105، واستبدالان. ثم نمذج إعادة الحساب: حضر مقترح A، وطبق تغيير B، وتجاهل مقترح A القديم، واحسب من 105. يجب أن تكون النتيجة 110. هذا نموذج حتمي لترتيبين، وليس محاكياً لكل قواعد Roblox الداخلية.
اختبر أيضاً غياب السجل، وعدداً صحيحاً عادياً، وسلسلة، وكسراً، وقيمة سالبة، واللانهاية، والحد الأعلى. يجب ألا يرفع تكرار المدخلات نفسها النتيجة بسبب حالة خفية. يجب أن يبقى الجدول الممرر بدلاً من العدد دون تغيير. يفحص الاختبار المحلي هذه الخصائص المحددة، لا موثوقية خادم إنتاج.
| الفحص المحلي | النتيجة المتوقعة |
|---|---|
| لا سجل؛ إضافة 5 | 5 |
| الحالي 100؛ إضافة 5 | 105 |
| الحالي 105؛ إضافة 5 | 110 |
| سلسلة بدلاً من عدد | nil: إلغاء |
| 999995؛ إضافة 5 | 1000000 |
| 999996؛ إضافة 5 | nil: إلغاء |
تكرار callback يختلف عن تكرار العملية #
يوافق تكرار callback داخل تحديث واحد المقترح مع الحالة المتغيرة. أما طلب مستقل ثانٍ لإضافة خمس فهو عملية أخرى، ويمكن أن يضيف خمساً إضافية حتى مع UpdateAsync. لذلك ليست الطريقة حماية مكتملة من المكافآت المكررة أو الطلبات المكررة أو معالجة شراء أكثر من مرة.
تحتاج المهمة التجارية إلى هوية محددة للعملية وقاعدة تفحص حالتها المكتملة مع تغيير البيانات. لا ننفذ هذه الآلية هنا. لا يحتفظ العداد العددي بتاريخ العمليات. سلم هذا القيد إلى محادثة التطوير التالية مع الكود حتى لا تُفهم التجربة على أنها نظام محفظة جاهز.
فشل الرد يترك سؤالاً آخر #
لا يثبت خطأ الشبكة تلقائياً أن الخلفية لم تكتب شيئاً. تصف Roblox كتابات ذات نتيجة مجهولة: قد يتم الحفظ دون وصول رد نجاح إلى الخادم. حينئذ قد يكرر طلب جديد غير مشروط لإضافة خمس العملية المستقلة. هذه حالة تختلف عن إعادة حساب callback الداخلية.
لا تضف حلقة إعادة محاولة لا نهائية إلى المثال. في نظام حقيقي عرّف أولاً التعامل مع النتيجة المجهولة وترتيب العمليات لكل مفتاح، ثم اختر محاولات محدودة للأخطاء المؤقتة وتأخيرات مناسبة. لا يولد اختبارنا المحلي فشل شبكة فعلياً في Roblox ولا يثبت حل المشكلة؛ تظل مهمة تصميم منفصلة.
طريقة واحدة لا تدير ملفاً كاملاً #
قد يحتوي الملف الكامل على حقول مترابطة وملكية جلسة وبيانات وصفية. لا يوضح مثال العدد الواحد كيف تحافظ عليها أثناء الكتابة. كما لا يعين مالك جلسة، ولا يمنع خادماً قديماً من حفظ لقطته بعد انتقال اللاعب إلى خادم جديد. نسبة هذه المهام جميعاً إلى UpdateAsync تخفي العمل المتبقي.
لا تستخدم العداد التعليمي لشراء Robux أو عملة اللعبة الفعلية. يتطلب حل الإنتاج مخطط بيانات خاصاً وفحوص توافق ومعالجة أخطاء وقواعد مكافأة. ابدأ بوثائق Roblox الخاصة بـ player data وpurchasing ثم افحص التنفيذ المحدد. هذه تجربة صغيرة لفهم التحويل، وليست مكتبة لإدارة الملفات.
جهز تسليماً صادقاً للمطور #
شارك رسم الترتيبين واسم المفتاح المعزول وقواعد الأعداد والاختبارات المحلية المكتملة. اكتب بوضوح أن الخوادم الحقيقية وتعارضات الشبكة لم تختبر. اذكر المتطلبات غير المحلولة منفصلة: نتيجة كتابة مجهولة، وتكرار عملية تجارية، وملكية جلسة، والحفاظ على بقية حقول الملف.
عند مقارنة الحلول اسأل: من أي قيمة يُحسب كل مقترح، وأي آثار تحدث خارج callback؟ يجب أن يفسر الجواب ترتيباً محدداً. إذا عمل الحل فقط دون كاتب ثانٍ، فمشكلة التعارض لم تُحل. احتفظ بهذا الدليل مع دليل الحفظ الأول للتمييز بين الوصول الأول إلى المخزن وتنسيق التغييرات اللاحقة.
المصادر الأصلية
Roblox Creator Hub — Data storesRoblox Creator Hub — GlobalDataStore
Roblox Creator Hub — Data store best practices