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

التطوير / ROBLOX

نسخة مخزون في Luau: لماذا تصل تعديلات table.clone إلى البيانات الأصلية

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

آخر تحديث:

حدد السلوك المطلوب للمسودة #

تخيل مخزونا تعليميا بمادتين: copper تحتوي count=3، وwood تحتوي count=1. تريد فتح مسودة للتعديل ووضع كمية النحاس فيها عند 9 مع احتفاظ البيانات الأصلية بالقيمة 3. يجب فحص قيمتين معا؛ عنوان على لوحة واحدة لا يكفي. اكتب أولا الزوج المتوقع original=3 وdraft=9، ثم اختر طريقة النسخ المفترض أن تحقق هذا الفصل تحديدا، لا مجرد استدعاء دالة دون ظهور خطأ.

المواد والحقول label وequipped مبتكرة للتمرين. إنها جداول عادية في الذاكرة وليست مخزون لاعب Roblox حقيقيا. تحققنا من المنطق المستقل في Luau دون توصيل GUI أو خادم أو RemoteEvent أو حفظ. يسمح هذا الحد بوصف صادق للمثال بدلا من تقديم نظام عناصر مكتمل. نجاح فحص النسخ لا يثبت أيضا تسليم موارد في تجربة منشورة أو تنفيذ تعديل فعلي في بيانات لاعب.

ميز الاسم الجديد عن الجدول الجديد #

كتابة draft=original تمنح الجدول نفسه اسما آخر. يصف الدليل الرسمي الجداول كقيم تصل إليها المتغيرات بالمراجع؛ لا ينشئ الإسناد العادي لقطة مستقلة. لذلك يمكن رؤية تعديل draft.copper.count أيضا عبر original.copper.count. المشكلة ليست رسم النص. يؤدي المتغيران إلى البيانات نفسها، فيغيب الفصل المطلوب قبل وجود واجهة أو ظهور لوحة لأي لاعب، ولا يصلح تحديث عنوان وحده العلاقة بينهما.

للفحص الأول أنشئ بيانات جديدة واستخدم الاسم الثاني لوضع الكمية عند 7. في اختبارنا المستقل تكون alias==source صحيحة، وتصبح source.copper.count مساوية 7. هذا مثال متحقق منه على كائن مشترك. لا تسمه نسخة لمجرد اختلاف اسم المتغير. أنشئ مجموعة جديدة بالكمية 3 قبل الفحص التالي، حتى لا يغير تعديل سابق معنى المقارنة أو يجعل نتيجتها تبدو وكأنها انطلقت من حالة أخرى.

افهم حدود table.clone #

تنشئ table.clone جدولا خارجيا جديدا، لكنها تنسخ محتواه سطحيا. إذا كانت قيمة عنصر جدولا آخر، يبقى مرجعه مشتركا. لذلك قد تكون draft~=original صحيحة وفي الوقت نفسه draft.copper==original.copper صحيحة أيضا. لا يوجد تناقض: الحاويتان الخارجيتان مختلفتان، بينما سجل داخلي واحد مشترك. يجب أن يفحص محرر الكميات العمق الذي يحدث فيه التعديل، لا اعتبار اختلاف المرجع الخارجي دليلا على استقلال كل البيانات.

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

الطريقةهل سجل copper مشترك؟
الإسنادنعم
table.cloneنعم
سجلات منفصلةلا

قارن المراجع والقيم بصورة منفصلة #

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

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

من البيانات إلى المسودةافتح الصورة بالحجم الكامل ↗
مخطط أصلي للمساعد المحدود بمستويين.

انسخ السجلات بحسب شكل بيانات صريح #

تدعم الدالة الأصلية copyInventoryDraft قاموسا بمعرفات عناصر نصية غير فارغة. كل قيمة جدول سجل بسيط، وحقوله المسماة تحتوي نصوصا أو أرقاما أو قيما منطقية فقط. تنسخ الدالة الحاوية الخارجية أولا ثم كل سجل على حدة. لهذا الشكل، تبقى الكمية والعنوان وequipped=false كقيم، ولا تشترك جداول السجلات بعد ذلك في هويتها مع الأصل. شكل البيانات جزء من تصميم الدالة، وليس تفصيلا اختياريا يضاف بعد استخدامها.

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

-- Teaching model only: string item IDs -> plain records -> scalar fields.
-- This is not a universal deep-copy function or a server validation layer.
local function copyInventoryDraft(original)
    assert(type(original) == "table", "Expected an inventory table")
    assert(getmetatable(original) == nil, "Root metatables are outside this model")
    local draft = table.clone(original)
    for id, record in pairs(original) do
        assert(type(id) == "string" and id ~= "", "Expected a nonempty item ID")
        assert(type(record) == "table", "Expected an item record")
        assert(getmetatable(record) == nil, "Record metatables are outside this model")
        for field, value in pairs(record) do
            assert(type(field) == "string", "Expected named record fields")
            local valueType = type(value)
            assert(valueType == "string" or valueType == "number" or valueType == "boolean",
                "Only scalar record fields are supported")
        end
        draft[id] = table.clone(record)
    end
    return draft
end

return copyInventoryDraft

شغل مثال الاستخدام الصغير #

في Luau المستقل احفظ المساعد باسم table-copy-example.luau، ومثال الاستخدام باسم table-copy-usage.luau في المجلد نفسه. يحصل الملف الثاني على الدالة عبر require بمسار نسبي، وينشئ سجلين تعليميين، ويغير draft.copper.count إلى 9. هذا ترتيب التشغيل الذي تحققنا منه. عند نقل الفكرة إلى ModuleScript في Roblox، راجع الحصول على الوحدة والبيئة بصورة منفصلة؛ الملف ذو المسار النسبي ليس إعداد Studio مكتملا أو مثبتا بالتجربة.

الخرج المتحقق منه هو GUIDEBOOK_TABLE_COPY_USAGE original=3 draft=9. تقارن assertions القيمتين أيضا وتحافظ على equipped=false. يعرض المقال المساعد المحدود كاملا واستخدامه القصير. لا يرسم أي منهما واجهة ولا يمنح عناصر. إذا اختلف خرجك، راجع الملف المشغل ومصدر الدالة وهل تغيرت البيانات الأصلية مسبقا. احتفظ بهذا السياق بدلا من الاعتماد على تسمية خرج مألوفة وحدها، أو افتراض أن أي رسالة نجاح تصف الحالة المطلوبة.

local copyInventoryDraft = require("./table-copy-example")
local original = {
    copper = {count = 3, label = "Copper", equipped = false},
    wood = {count = 1, label = "Wood", equipped = true},
}
local draft = copyInventoryDraft(original)
draft.copper.count = 9
assert(original.copper.count == 3)
assert(draft.copper.count == 9)
assert(draft.copper.equipped == false)
print("GUIDEBOOK_TABLE_COPY_USAGE original=" .. original.copper.count .. " draft=" .. draft.copper.count)

افحص التعديلات من الجانبين #

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

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

النتيجة المتحقق منهاافتح الصورة بالحجم الكامل ↗
نتيجة Luau مستقل؛ ليست مخزون لعبة حقيقيا.

لا تعتبر freeze استقلالا عميقا #

توضح المكتبة الرسمية أن table.freeze تغير حالة الجدول المعطى في مكانه، ولا تجمد القيم الداخلية تلقائيا. يجمد اختبارنا المخزون الخارجي ويؤكد أن copper الداخلية ما زالت قابلة للتعديل؛ تغيير count يظل ممكنا. لذلك لا تثبت حاوية خارجية غير قابلة للتغيير حماية كل البيانات الموجودة داخلها من التحرير أو فصلها عن مراجع أخرى. هذه خصائص مختلفة تحتاج ملاحظات خاصة بكل شرط، لا علامة واحدة عامة.

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

حافظ على حدود التحقق المستقل #

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

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

الحالةالنتيجة
تعديل المسودةالأصل يبقى3
تعديل الأصلالمسودة لا تتغير
كائن أعمقيرفضه العقد
GUI والحفظلم تختبر بعد

اختر النسخ لفعل محدد #

ينبغي للسجل النهائي تحديد المستوى المنسوخ والمراجع التي تبقى مشتركة والتعديلات التي عزلت فعلا. يطابق original=3 وdraft=9 مهمة التحرير المختارة في تمريننا. أما إذا أردت قائمة جديدة لمراجع إلى كائنات موجودة، فقد يكون النسخ السطحي مقصودا. حدد الفعل وأثره المتوقع قبل اختيار الطريقة، بدلا من استخدام النسخ الأكثر تعقيدا تلقائيا لكل البيانات وتجاهل العلاقات التي يحتاج مشروعك الحفاظ عليها.

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

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

Luau — Standard Library
Roblox Creator Hub — Tables