Roblox Guidebookقاعدة المعرفة
العربية ⌄

التطوير / ROBLOX

UpdateAsync في Roblox: لماذا قد تفقد عمليتا حفظ تغييراً واحداً

يقرأ خادمان خياليان لورشة القيمة 100 ويضيف كل منهما 5، لكن النتيجة المحفوظة تبقى 105. نفصل استبدال نسخة قديمة عن تحويل الحالة الحالية، ونرسم تسلسل التعارض، ونختبر دالة محلية دون الاتصال بمخزن حقيقي.

آخر تحديث:

افحص التسلسل قبل اتهام زر الحفظ #

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

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

ارسم تسلسل الكتابة من نسخة قديمة #

اكتب خمسة أسطر: يحتوي المخزن على 100؛ يقرأ المعالج A القيمة 100؛ يقرأ B القيمة 100؛ يكتب A القيمة 105؛ يكتب B مقترحه الخاص 105. لا يحتاج B إلى نية ضارة لفقدان زيادة A. إنه يستبدل السجل بحساب أُعد قبل الكتابة الأولى. يفترض أن تعطي زيادتان القيمة 110، بينما يترك استبدالان متساويان القيمة 105.

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

النسخ القديمة تفقد تغييراًافتح الصورة بالحجم الكامل ↗
ترتيب خيالي للاستبدال يوضح فقدان تغيير، وليس سجل خوادم حقيقية.
الخطوةالفعلالقيمة المحفوظة
1البداية100
2يقرأ A القيمة 100100
3يقرأ B القيمة 100100
4يكتب A القيمة 105105
5يكتب B القيمة 105105

الاستبدال والتحويل يعبران عن قصدين مختلفين #

تضع SetAsync قيمة المفتاح دون قراءته أولاً ضمن هذه الطريقة. يناسب ذلك استبدالاً بقيمة معروفة لا تعتمد على الحالة السابقة. أما إضافة خمس إلى العداد الحالي فتتطلب الانتباه إلى أساس الحساب. توصي Roblox باستخدام UpdateAsync عندما تعتمد الكتابة على القيمة الحالية أو يمكن لعدة خوادم كتابة المفتاح نفسه.

تغيير اسم الطريقة فقط مع إعادة 105 المحسوبة مسبقاً من callback لا يصلح التسلسل. لا يزال المقترح مبنياً على النسخة القديمة. احسب من الوسيط current الذي يصل إلى callback. اشرح الفرق قبل نقله إلى جدول مخزون: تحويل حالة حديثة ليس إعادة إرسال لقطة قديمة تخصك.

قد يُستدعى callback مجدداً #

إذا تغير المفتاح بواسطة خادم آخر بين القراءة والكتابة، تستطيع UpdateAsync تجاهل المقترح السابق واستدعاء التحويل مرة أخرى. لذلك لا يعني استدعاء الطريقة مرة واحدة تنفيذ callback مرة واحدة. في ترتيبنا الخيالي يقترح A القيمة 105، ويحفظ B القيمة 105 أولاً، ثم يعيد A الحساب من 105 ليقترح 110.

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

إعادة الحساب من الحالة الحاليةافتح الصورة بالحجم الكامل ↗
إعادة حساب خيالية بعد كاتب آخر. النموذج المحلي لا يتحقق من شبكة Roblox الفعلية.

دالة تعليمية دون حالة خفية #

يوفر 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 الداخلية.

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

الفحص المحليالنتيجة المتوقعة
لا سجل؛ إضافة 55
الحالي 100؛ إضافة 5105
الحالي 105؛ إضافة 5110
سلسلة بدلاً من عددnil: إلغاء
999995؛ إضافة 51000000
999996؛ إضافة 5nil: إلغاء

تكرار callback يختلف عن تكرار العملية #

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

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

فشل الرد يترك سؤالاً آخر #

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

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

طريقة واحدة لا تدير ملفاً كاملاً #

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

لا تستخدم العداد التعليمي لشراء Robux أو عملة اللعبة الفعلية. يتطلب حل الإنتاج مخطط بيانات خاصاً وفحوص توافق ومعالجة أخطاء وقواعد مكافأة. ابدأ بوثائق Roblox الخاصة بـ player data وpurchasing ثم افحص التنفيذ المحدد. هذه تجربة صغيرة لفهم التحويل، وليست مكتبة لإدارة الملفات.

جهز تسليماً صادقاً للمطور #

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

عند مقارنة الحلول اسأل: من أي قيمة يُحسب كل مقترح، وأي آثار تحدث خارج callback؟ يجب أن يفسر الجواب ترتيباً محدداً. إذا عمل الحل فقط دون كاتب ثانٍ، فمشكلة التعارض لم تُحل. احتفظ بهذا الدليل مع دليل الحفظ الأول للتمييز بين الوصول الأول إلى المخزن وتنسيق التغييرات اللاحقة.

المصادر الأصلية

Roblox Creator Hub — Data stores
Roblox Creator Hub — GlobalDataStore
Roblox Creator Hub — Data store best practices