تسمح جداول الميتا للجداول بأن تصبح أكثر قوة من ذي قبل. يتم ربطها بالبيانات وتحتوي على قيم تُسمى طرق الميتا. يتم استدعاء طرق الميتا عندما يتم استخدام إجراء معين مع البيانات المرتبطة بها.
التلاعب بجداول الميتا
الوظيفتان الرئيسيتان لإضافة والعثور على جدول الميتا الخاص بجدول هي setmetatable() و getmetatable().
local x = {}
local metaTable = {} -- جداول الميتا هي جداول أيضًا!
setmetatable(x, metaTable) -- أعطِ x جدول ميتا يسمى metaTable!
print(getmetatable(x)) --> table: [hexadecimal memory address]تقوم دالة setmetatable() أيضًا بإرجاع الجدول الذي تقوم بتعيين جدول الميتا له، لذا فإن هذين السكريبتين يقومان بنفس الشيء:
local x = {}
setmetatable(x, {})local x = setmetatable({}, {})طرق الميتا
طرق الميتا هي الوظائف التي يتم تخزينها داخل جدول الميتا. يمكن أن تتراوح من استدعاء جدول، إلى إضافة جدول، إلى حتى قسمة الجداول أيضًا. إليك قائمة بطرق الميتا المتاحة:
| الطريقة | الوصف |
|---|---|
| __index(table, index) | يتم استدعاؤه عندما يتم فهرسة table[index]، إذا كانت table[index] هي nil. يمكن أيضًا تعيينه إلى جدول، وفي هذه الحالة سيتم فهرسة ذلك الجدول. |
| __newindex(table, index, value) | يتم استدعاؤه عندما يتم محاولة تعيين table[index] (table[index] = value)، إذا كانت table[index] هي nil. يمكن أيضًا تعيينه إلى جدول، وفي هذه الحالة سيتم فهرسة ذلك الجدول. |
| __call(table, ...) | يتم استدعاؤه عندما يتم استدعاء الجدول كدالة، ... هي المعاملات التي تم تمريرها. |
| __concat(table, value) | يتم استدعاؤه عندما يتم استخدام عامل الدمج .. على الجدول. |
| __unm(table) | يتم استدعاؤه عندما يتم استخدام عامل السالب الأحادي – على الجدول. |
| __add(table, value) | عامل الجمع +. |
| __sub(table, value) | عامل الطرح –. |
| __mul(table, value) | عامل الضرب *. |
| __div(table, value) | عامل القسمة /. |
| __idiv(table, value) | عامل القسمة الأرضية //. |
| __mod(table, value) | عامل المودولوس %. |
| __pow(table, value) | عامل الأس ^. |
| __tostring(table) | يتم استدعاؤه عندما يتم استدعاء tostring على الجدول. |
| __metatable | إذا كانت موجودة، تقفل جدول الميتا بحيث ستعيد getmetatable() هذا بدلاً من جدول الميتا و setmetatable() ستظهر خطأ. قيمة غير دالة. |
| __eq(table, value) | عامل المساواة ==¹ |
| __lt(table, value) | عامل أقل من <¹ |
| __le(table, value) | عامل أقل من أو يساوي <=¹ |
| __mode | يستخدم في الجداول الضعيفة، يعلن ما إذا كانت المفاتيح و/أو القيم في جدول ضعيفة. انظر استخدام الجداول الضعيفة. |
| __len(table) | يتم استدعاؤه عندما يتم استخدام عامل الطول # على الكائن. |
| __iter(table) | يستخدم للإشارة إلى مكرر مخصص عند استخدام التكرار العام. |
يجب ملاحظة أنه عند كتابة وظائف لطرق الميتا الحسابية أو العلائقية، فإن المعاملين الوظيفيين يمكن تبديلهما بين الجدول الذي استدعى طريقة الميتا والقيمة الأخرى. على سبيل المثال، عند إجراء عمليات متجهية مع مقاييس، فإن القسمة ليست تبادلية. لذلك إذا كنت تكتب طرق ميتا لفئة vector2 الخاصة بك، يجب أن تكون حذرًا في حساب أي من السيناريوهين.
local vector2 = {__type = "vector2"}
local mt = {__index = vector2}
function mt.__div(a, b)
if type(a) == "number" then
-- a هو مقياس، b هو متجه
local scalar, vector = a, b
return vector2.new(scalar / vector.x, scalar / vector.y)
elseif type(b) == "number" then
-- a هو متجه، b هو مقياس
local vector, scalar = a, b
return vector2.new(vector.x / scalar, vector.y / scalar)
elseif (a.__type and a.__type == "vector2" and b.__type and b.__type == "vector2") then
-- كلا من a و b هما متجهات
return vector2.new(a.x / b.x, a.y / b.y)
end
end
function mt.__tostring(t)
return t.x .. ", " .. t.y
end
function vector2.new(x, y)
local self = setmetatable({}, mt)
self.x = x or 0
self.y = y or 0
return self
end
local a = vector2.new(10, 5)
local b = vector2.new(-3, 4)
print(a / b) -- -3.3333333333333, 1.25
print(b / a) -- -0.3, 0.8
print(2 / a) -- 0.2, 0.4
print(a / 2) -- 5, 2.5الاستخدام
هناك العديد من الطرق لاستخدام جداول الميتا.
تطبيق عامل السالب الأحادي على الجداول
تتعامل طريقة الميتا __unm مع عامل السالب الأحادي. استخدمه لنفي كل قيمة في جدول:
local metatable = {
__unm = function(t) -- __unm هو لعامل السالب -
local negated = {}
for key, value in t do
negated[key] = -value -- نفي جميع القيم في هذا الجدول
end
return negated -- إرجاع الجدول
end
}
local table1 = setmetatable({10, 11, 12}, metatable)
print(table.concat(-table1, "; ")) --> -10; -11; -12فهرسة القيم المفقودة من جدول آخر
إليك طريقة مثيرة للاهتمام للإعلان عن الأشياء باستخدام __index:
local metatable = {
__index = {x = 1}
}
local t = setmetatable({}, metatable)
print(t.x) --> 1تم استدعاء __index عندما تم فهرسة x في الجدول ولم يتم العثور عليه. ثم بحثت Luau في جدول __index عن فهرس يسمى x، وعندما وجدته، أعادته.
استدعاء جدول كدالة
يمكن لجداول الميتا القيام بأكثر من مجرد توفير قيمة مفقودة. خذ هذا كمثال:
local t = {10, 20, 30}
print(t(5))عادةً لا يمكنك استدعاء جدول، ولكن مع جداول الميتا يمكنك:
local metatable = {
__call = function(t, param)
local sum = {}
for i, value in ipairs(t) do
sum[i] = value + param -- أضف المعامل (5) إلى القيمة، ثم ضعها في الجدول الجديد (t).
end
return unpack(sum) -- إرجاع قيم الجدول الفردية
end
}
local t = setmetatable({10, 20, 30}, metatable)
print(t(5)) --> 15 25 35تطبيق عامل الجمع على الجداول
يمكنك القيام بالكثير من الأشياء الأخرى أيضًا، مثل إضافة الجداول:
local table1 = {10, 11, 12}
local table2 = {13, 14, 15}
for k, v in table1 + table2 do
print(k, v)
endسيظهر هذا خطأ يقول إنك تحاول إجراء حساب على جدول، ولكنها تعمل عند المحاولة مع جدول ميتا:
local metatable = {
__add = function(t1, t2)
local sum = {}
for key, value in t1 do
sum[key] = value
end
for key, value in t2 do
if sum[key] then
sum[key] += value
else
sum[key] = value
end
end
return sum
end
}
local table1 = setmetatable({10, 11, 12}, metatable)
local table2 = setmetatable({13, 14, 15}, metatable)
for k, v in table1 + table2 do
print(k, v)
endتعيين القيم دون استدعاء طرق الميتا
عند اللعب مع جداول الميتا، قد تواجه بعض المشاكل. إذا كنت بحاجة إلى استخدام طريقة __index لإنشاء قيم جديدة في جدول، ولكن جدول الميتا لذلك الجدول يحتوي أيضًا على طريقة __newindex، فستحتاج إلى استخدام الدالة المدمجة في Luau rawset() لتعيين القيمة دون استدعاء أي طرق ميتا. خذ الكود التالي كمثال لما يحدث إذا لم تستخدم هذه الوظائف.
local t = setmetatable({}, {
__index = function(self, i)
self[i] = i * 10
return self[i]
end,
__newindex = function(self, i, v)
-- لا تقم بتعيين القيم للجدول بالطريقة العادية
end
})
print(t[1]) -- يسبب تجاوز المكدستحدث تجاوزات المكدس عندما تحاول استدعاء دالة من نفسها مرات عديدة جدًا. في دالة __index أعلاه، يتم تعيين self[i] إلى قيمة، لذا عندما تصل إلى السطر التالي، يجب أن يكون self[i] موجودًا ومن المفترض ألا يستدعي طريقة __index الميتا. المشكلة هي أن __newindex لا تسمح لك بتعيين القيمة. وجودها يمنع إضافة القيم إلى الجدول بالطريقة القياسية t[i] = v. لتجاوز ذلك، استخدم دالة rawset():
local t = setmetatable({}, {
__index = function(self, i)
rawset(self, i, i * 10)
return self[i]
end,
__newindex = function(self, i, v)
-- لا تقم بتعيين القيم للجدول بالطريقة العادية
end
})
print(t[1]) --> 10استخدام الجداول الضعيفة
تحتفظ المتغيرات التي تحتوي على جداول، وظائف، خيوط، و بيانات مستخدم، بما في ذلك Instances، بمراجع لتلك الكائنات بدلاً من نسخها. يؤدي تعيين كائن إلى متغير آخر إلى إنشاء مرجع آخر لنفس الكائن:
local map = {key = 1}
local mapReference = map
print(map == mapReference) --> true
print(map == {key = 1}) --> false
local function func() end
local funcReference = func
print(func == funcReference) --> true
local part = Instance.new("Part")
local partReference = part
print(part == partReference) --> trueتكون الكائنات التي لم تعد قابلة للوصول من خلال المراجع القوية مؤهلة لجمع القمامة. لا يجعل تعيين متغير إلى nil كائنًا مؤهلاً لجمع القمامة إذا كانت هناك مرجع قوي آخر لا يزال يشير إلى ذلك الكائن:
local child = {}
local parent = {Child = child}
child = nil
print(parent.Child) --> table: [hexadecimal memory address]على الرغم من أن child لم يعد يشير إلى جدول الطفل، إلا أن parent.Child لا يزال يشير إليه. طالما أن الجدول الأب لا يزال قابلاً للوصول، فإن مرجعه يحتفظ أيضًا بجدول الطفل قابلاً للوصول.
تحتفظ الجدول الضعيف بالمفاتيح، القيم، أو كليهما دون منع جامع القمامة من استعادة الكائنات التي لم تعد قابلة للوصول. قم بتعيين حقل __mode لجدول الميتا للتحكم في أي المراجع ضعيفة:
- "k" تجعل المفاتيح ضعيفة.
- "v" تجعل القيم ضعيفة.
- "kv" أو "vk" تجعل كل من المفاتيح والقيم ضعيفة.
يخلق الكود التالي جدولًا بقيم ضعيفة:
local children = setmetatable({}, {
__mode = "v",
})
do
local child = {}
children.example = child
endبعد انتهاء كتلة do، تصبح القيمة المخزنة في children.example بلا مراجع قوية وتصبح مؤهلة لجمع القمامة. عندما يستعيد جامع القمامة جدول الطفل، تزيل Luau إدخاله من children. جمع القمامة غير محدد، لذا لا تعتمد على متى تختفي الإدخالات.
أي وضع تتركه يبقى قويًا. تبقى المفاتيح في جدول __mode = "v" مراجع قوية، لذا يبقى الكائن المخزن كمفتاح قابلاً للوصول وتبقى إدخاله في الجدول.
بالنسبة لـ Instances، ينطبق جمع القمامة على مرجع Luau بدلاً من الكائن نفسه. يخزن المحرك بيانات المثيل خارج VM Luau، لذا فإن جمع مرجع Luau لا يحذف المثيل، والمثيل الذي يبقى في نموذج البيانات لا يحتفظ بمرجع Luau الخاص به. لإصدار مثيل، استدعِ Instance:Destroy() وامسح كل مرجع متبقي له.
استخدام نوع البيانات المجموعات
المجموعة هي مجموعة من العناصر بدون ترتيب وبدون عناصر مكررة. إما أن يكون العنصر موجودًا أو غير موجود ضمن المجموعة. باستخدام جداول الميتا، يمكنك بناء المجموعات والتلاعب بها ضمن سكريبتات Luau.
الطرق الأساسية
يتضمن الكود التالي وظائف مجموعة أساسية، مما يتيح لك بناء مجموعات جديدة، إضافة وإزالة عنصر، التحقق مما إذا كانت المجموعة تحتوي على عنصر، وإخراج محتويات المجموعة.
local Set = {}
Set.__index = Set
-- دالة لبناء مجموعة من قائمة اختيارية من العناصر
function Set.new(items)
local newSet = {}
for key, value in items or {} do
newSet[value] = true
end
return setmetatable(newSet, Set)
end
-- دالة لإضافة عنصر إلى مجموعة
function Set:add(item)
self[item] = true
end
-- دالة لإزالة عنصر من مجموعة
function Set:remove(item)
self[item] = nil
end
-- دالة للتحقق مما إذا كانت المجموعة تحتوي على عنصر
function Set:contains(item)
return self[item] == true
end
-- دالة لإخراج المجموعة كقائمة مفصولة بفواصل لأغراض التصحيح
function Set:output()
local elems = {}
for key, value in self do
table.insert(elems, tostring(key))
end
print(table.concat(elems, ", "))
endإنشاء مجموعة
يمكن بناء مجموعة جديدة عن طريق استدعاء Set.new() مع مصفوفة اختيارية من العناصر لإضافتها.
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})لاحظ أنه وفقًا للتعريف، لا تحتوي المجموعة على مفهوم الترتيب.
إضافة عنصر
يمكن إضافة عنصر إلى مجموعة موجودة عبر طريقة Set:add().
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})
fruits:add("Mango")إزالة عنصر
لإزالة عنصر من مجموعة، استدعِ Set:remove() مع اسم العنصر.
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})
fruits:remove("Orange")التحقق من وجود عنصر
للتحقق مما إذا كانت المجموعة تحتوي على عنصر معين، استخدم Set:contains().
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})
local result1 = fruits:contains("Cherry")
print(result1) -- true
local result2 = fruits:contains("Watermelon")
print(result2) -- falseطرق إضافية
يمكن تنفيذ عمليات أخرى مفيدة للمجموعات، مما يتيح لك مقارنة العناصر بين المجموعات، دمج المجموعات، أو طرح مجموعة من أخرى.
التقاطع
عند اعتبار المجموعات كرسوم بيانية فين، يمكنك الحصول على التقاطع بين مجموعتين كما يلي، مما يعني العناصر التي تظهر في كلا المجموعتين.
local function getIntersection(set1, set2)
local result = Set.new()
for key, value in set1 do
if set2:contains(key) then
result:add(key)
end
end
return result
end
local freshFruits = Set.new({"Mango", "Lemon", "Orange", "Cherry", "Lime", "Peach"})
local frozenFruits = Set.new({"Mango", "Peach", "Pineapple"})
local commonFruits = getIntersection(freshFruits, frozenFruits)
commonFruits:output() -- Mango, Peachالاتحاد
يمكنك الحصول على الاتحاد بين مجموعتين باستخدام الدالة التالية، مما يعني مجموعة من العناصر في كلا المجموعتين بدون تكرار. لاحظ أن هذه الدالة تستخدم طريقة الميتا __add لتوفير اختصار جمع set1 + set2.
function Set:__add(otherSet)
local result = Set.new()
for entry in self do
result[entry] = true
end
for entry in otherSet do
result[entry] = true
end
return result
end
local sweetFruits = Set.new({"Apple", "Mango", "Cherry", "Peach"})
local sourFruits = Set.new({"Lemon", "Lime"})
local allFruits = sweetFruits + sourFruits
allFruits:output() -- Peach, Lime, Apple, Cherry, Lemon, Mangoالطرح
يمكنك إزالة جميع العناصر في مجموعة من العناصر في مجموعة أخرى عبر الدالة التالية. مشابهة للدالة أعلاه، تستخدم هذه الدالة طريقة الميتا __sub لتوفير اختصار الطرح set1 - set2.
function Set:__sub(otherSet)
local result = Set.new()
for entry in self do
result[entry] = true
end
for entry in otherSet do
result[entry] = nil
end
return result
end
local allFruits = Set.new({"Apple", "Lemon", "Mango", "Cherry", "Lime", "Peach"})
local sourFruits = Set.new({"Lemon", "Lime"})
local sweetFruits = allFruits - sourFruits
sweetFruits:output() -- Mango, Apple, Cherry, Peach