Les métatables permettent aux tables de devenir plus puissantes qu'auparavant. Elles sont attachées aux données et contiennent des valeurs appelées métaméthodes. Les métaméthodes sont déclenchées lorsqu'une certaine action est utilisée avec la donnée à laquelle elles sont attachées.
Manipuler les métatables
Les deux fonctions principales pour ajouter et trouver la métatable d'une table sont setmetatable() et getmetatable().
local x = {}
local metaTable = {} -- Les métatables sont aussi des tables !
setmetatable(x, metaTable) -- Donne à x une métatable appelée metaTable !
print(getmetatable(x)) --> table: [adresse mémoire hexadécimale]La fonction setmetatable() retourne également la table à laquelle vous définissez la métatable, donc ces deux scripts font la même chose :
local x = {}
setmetatable(x, {})local x = setmetatable({}, {})Métaméthodes
Les métaméthodes sont les fonctions qui sont stockées à l'intérieur d'une métatable. Elles peuvent aller de l'appel d'une table, à l'addition d'une table, jusqu'à la division de tables également. Voici la liste des métaméthodes disponibles :
| Méthode | Description |
|---|---|
| __index(table, index) | Se déclenche lorsque table[index] est indexé, si table[index] est nil. Peut également être défini sur une table, auquel cas cette table sera indexée. |
| __newindex(table, index, value) | Se déclenche lorsque table[index] essaie d'être défini (table[index] = value), si table[index] est nil. Peut également être défini sur une table, auquel cas cette table sera indexée. |
| __call(table, ...) | Se déclenche lorsque la table est appelée comme une fonction, ... étant les arguments qui ont été passés. |
| __concat(table, value) | Se déclenche lorsque l'opérateur de concaténation .. est utilisé sur la table. |
| __unm(table) | Se déclenche lorsque l'opérateur unaire – est utilisé sur la table. |
| __add(table, value) | L'opérateur d'addition +. |
| __sub(table, value) | L'opérateur de soustraction –. |
| __mul(table, value) | L'opérateur de multiplication *. |
| __div(table, value) | L'opérateur de division /. |
| __idiv(table, value) | L'opérateur de division entière //. |
| __mod(table, value) | L'opérateur de modulo %. |
| __pow(table, value) | L'opérateur d'exponentiation ^. |
| __tostring(table) | Se déclenche lorsque tostring est appelé sur la table. |
| __metatable | Si présent, verrouille la métatable afin que getmetatable() retourne ceci au lieu de la métatable et que setmetatable() génère une erreur. Valeur non fonctionnelle. |
| __eq(table, value) | L'opérateur d'égalité ==¹ |
| __lt(table, value) | L'opérateur de comparaison inférieure <¹ |
| __le(table, value) | L'opérateur de comparaison inférieure ou égale <=¹ |
| __mode | Utilisé dans les tables faibles, déclarant si les clés et/ou les valeurs d'une table sont faibles. Voir Utiliser des tables faibles. |
| __len(table) | Se déclenche lorsque l'opérateur de longueur # est utilisé sur l'objet. |
| __iter(table) | Utilisé pour désigner un itérateur personnalisé lors de l'utilisation d'itération généralisée. |
Il convient de noter que lors de l'écriture de fonctions pour les métaméthodes arithmétiques ou relationnelles, les deux paramètres de fonction sont interchangeables entre la table qui a déclenché la métaméthode et l'autre valeur. Par exemple, lors de l'exécution d'opérations vectorielles avec des scalaires, la division n'est pas commutative. Par conséquent, si vous écriviez des métaméthodes pour votre propre classe vector2, vous voudriez faire attention à prendre en compte les deux scénarios.
local vector2 = {__type = "vector2"}
local mt = {__index = vector2}
function mt.__div(a, b)
if type(a) == "number" then
-- a est un scalaire, b est un vecteur
local scalar, vector = a, b
return vector2.new(scalar / vector.x, scalar / vector.y)
elseif type(b) == "number" then
-- a est un vecteur, b est un scalaire
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 et b sont tous deux des vecteurs
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.5Utilisation
Il existe de nombreuses façons d'utiliser les métatables.
Appliquer l'opérateur moins unaire aux tables
La métaméthode __unm gère l'opérateur moins unaire. Utilisez-la pour négativer chaque valeur dans une table :
local metatable = {
__unm = function(t) -- __unm est pour l'opérateur unaire -
local negated = {}
for key, value in t do
negated[key] = -value -- négate toutes les valeurs dans cette table
end
return negated -- retourne la table
end
}
local table1 = setmetatable({10, 11, 12}, metatable)
print(table.concat(-table1, "; ")) --> -10; -11; -12Indexer des valeurs manquantes à partir d'une autre table
Voici une manière intéressante de déclarer des choses en utilisant __index :
local metatable = {
__index = {x = 1}
}
local t = setmetatable({}, metatable)
print(t.x) --> 1__index a été déclenché lorsque x a été indexé dans la table et n'a pas été trouvé. Luau a ensuite recherché dans la table __index un index appelé x, et, en en trouvant un, l'a retourné.
Appeler une table comme une fonction
Les métatables peuvent faire beaucoup plus que fournir une valeur manquante. Prenez cet exemple :
local t = {10, 20, 30}
print(t(5))Typiquement, vous ne pouvez pas appeler une table, mais avec les métatables, vous pouvez :
local metatable = {
__call = function(t, param)
local sum = {}
for i, value in ipairs(t) do
sum[i] = value + param -- Ajoute l'argument (5) à la valeur, puis le place dans la nouvelle table (t).
end
return unpack(sum) -- Retourne les valeurs individuelles de la table
end
}
local t = setmetatable({10, 20, 30}, metatable)
print(t(5)) --> 15 25 35Appliquer l'opérateur d'addition aux tables
Vous pouvez faire beaucoup plus également, comme additionner des tables :
local table1 = {10, 11, 12}
local table2 = {13, 14, 15}
for k, v in table1 + table2 do
print(k, v)
endCela générera une erreur disant que vous essayez d'effectuer une opération arithmétique sur une table, mais cela fonctionne lorsqu'il est tenté avec une métatable :
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)
endDéfinir des valeurs sans invoquer les métaméthodes
Lorsque vous jouez avec des métatables, vous pouvez rencontrer certains problèmes. Si vous devez utiliser la métaméthode __index pour créer de nouvelles valeurs dans une table, mais que la métatable de cette table a également une métaméthode __newindex, vous voudrez utiliser la fonction intégrée de Luau rawset() pour définir la valeur sans invoquer de métaméthodes. Prenez le code suivant comme exemple de ce qui se passe si vous n'utilisez pas ces fonctions.
local t = setmetatable({}, {
__index = function(self, i)
self[i] = i * 10
return self[i]
end,
__newindex = function(self, i, v)
-- Ne pas définir de valeurs à la table de la manière normale
end
})
print(t[1]) -- Provoque un débordement de pileLes débordements de pile se produisent lorsque vous essayez d'appeler une fonction depuis elle-même trop de fois. Dans la fonction __index ci-dessus, self[i] est défini sur une valeur, donc lorsqu'il arrive à la ligne suivante, self[i] devrait exister et ne devrait pas appeler la métaméthode __index. Le problème est que __newindex ne vous permet pas de définir la valeur. Sa présence empêche les valeurs d'être ajoutées à la table avec la méthode standard t[i] = v. Pour contourner cela, utilisez la fonction rawset() :
local t = setmetatable({}, {
__index = function(self, i)
rawset(self, i, i * 10)
return self[i]
end,
__newindex = function(self, i, v)
-- Ne pas définir de valeurs à la table de la manière normale
end
})
print(t[1]) --> 10Utiliser des tables faibles
Les variables qui contiennent des tables, des fonctions, des threads et des userdata, y compris Instances, stockent des références à ces objets au lieu de copies d'eux. Assigner un objet à une autre variable crée une autre référence au même objet :
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) --> trueLes objets qui ne sont plus accessibles par des références fortes deviennent éligibles à la collecte des ordures. Définir une variable à nil ne rend pas un objet éligible à la collecte des ordures si une autre référence forte pointe toujours vers cet objet :
local child = {}
local parent = {Child = child}
child = nil
print(parent.Child) --> table: [adresse mémoire hexadécimale]Bien que child ne fasse plus référence à la table enfant, parent.Child le fait toujours. Tant que la table parent reste accessible, sa référence garde la table enfant accessible également.
Une table faible conserve des clés, des valeurs ou les deux sans empêcher le ramasse-miettes de récupérer des objets autrement inaccessibles. Définissez le champ __mode de la métatable de la table pour contrôler quelles références sont faibles :
- "k" rend les clés faibles.
- "v" rend les valeurs faibles.
- "kv" ou "vk" rend les clés et les valeurs faibles.
Le code suivant crée une table avec des valeurs faibles :
local children = setmetatable({}, {
__mode = "v",
})
do
local child = {}
children.example = child
endAprès la fin du bloc do, la valeur stockée dans children.example n'a plus de références fortes et devient éligible à la collecte des ordures. Lorsque le ramasse-miettes récupère la table enfant, Luau supprime son entrée de children. La collecte des ordures est non déterministe, donc ne comptez pas sur le moment où l'entrée disparaît.
Tout mode que vous laissez de côté reste fort. Les clés dans une table __mode = "v" restent des références fortes, donc un objet stocké comme clé reste accessible et son entrée reste dans la table.
Pour Instances, la collecte des ordures s'applique à la référence Luau plutôt qu'à l'objet lui-même. Le moteur stocke les données d'instance en dehors de la VM Luau, donc collecter une référence Luau ne supprime pas l'instance, et une instance qui reste dans le modèle de données ne garde pas sa référence Luau vivante. Pour libérer une instance, appelez Instance:Destroy() et effacez toutes les références restantes à celle-ci.
Utiliser le type de données set
Un set est une collection d'éléments sans ordre et sans éléments dupliqués. Un élément est ou n'est pas contenu dans un set. En utilisant des métatables, vous pouvez construire et manipuler des sets dans des scripts Luau.
Méthodes de base
Le code suivant inclut des fonctionnalités de base pour les sets, vous permettant de construire de nouveaux sets, d'ajouter et de supprimer un élément, de vérifier si un set contient un élément, et d'afficher le contenu d'un set.
local Set = {}
Set.__index = Set
-- Fonction pour construire un set à partir d'une liste d'éléments optionnelle
function Set.new(items)
local newSet = {}
for key, value in items or {} do
newSet[value] = true
end
return setmetatable(newSet, Set)
end
-- Fonction pour ajouter un élément à un set
function Set:add(item)
self[item] = true
end
-- Fonction pour supprimer un élément d'un set
function Set:remove(item)
self[item] = nil
end
-- Fonction pour vérifier si un set contient un élément
function Set:contains(item)
return self[item] == true
end
-- Fonction pour afficher le set sous forme de liste délimitée par des virgules pour le débogage
function Set:output()
local elems = {}
for key, value in self do
table.insert(elems, tostring(key))
end
print(table.concat(elems, ", "))
endCréer un set
Un nouveau set peut être construit en appelant Set.new() avec un tableau d'éléments optionnel à ajouter.
local fruits = Set.new({"Pomme", "Citron", "Orange", "Cerise", "Citron vert", "Pêche"})Notez qu'en définition, un set n'a pas de concept d'ordre.
Ajouter un élément
Ajouter un élément à un set existant peut se faire via la méthode Set:add().
local fruits = Set.new({"Pomme", "Citron", "Orange", "Cerise", "Citron vert", "Pêche"})
fruits:add("Mangue")Supprimer un élément
Pour supprimer un élément d'un set, appelez Set:remove() avec le nom de l'élément.
local fruits = Set.new({"Pomme", "Citron", "Orange", "Cerise", "Citron vert", "Pêche"})
fruits:remove("Orange")Vérifier un élément
Pour vérifier si un set contient un élément spécifique, utilisez Set:contains().
local fruits = Set.new({"Pomme", "Citron", "Orange", "Cerise", "Citron vert", "Pêche"})
local result1 = fruits:contains("Cerise")
print(result1) -- true
local result2 = fruits:contains("Pastèque")
print(result2) -- falseMéthodes supplémentaires
D'autres opérations utiles peuvent être mises en œuvre pour les sets, vous permettant de comparer des éléments entre des sets, de combiner des sets ou de soustraire un set d'un autre.
Intersection
En considérant les sets comme des diagrammes de Venn, vous pouvez obtenir l'intersection de deux sets comme suit, signifiant les éléments qui apparaissent dans les deux sets.
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({"Mangue", "Citron", "Orange", "Cerise", "Citron vert", "Pêche"})
local frozenFruits = Set.new({"Mangue", "Pêche", "Ananas"})
local commonFruits = getIntersection(freshFruits, frozenFruits)
commonFruits:output() -- Mangue, PêcheUnion
Vous pouvez obtenir l'union de deux sets avec la fonction suivante, signifiant une collection des éléments dans les deux sets sans doublons. Notez que cette fonction utilise la méthode de métatable __add pour fournir un raccourci d'addition de 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({"Pomme", "Mangue", "Cerise", "Pêche"})
local sourFruits = Set.new({"Citron", "Citron vert"})
local allFruits = sweetFruits + sourFruits
allFruits:output() -- Pêche, Citron vert, Pomme, Cerise, Citron, MangueSoustraction
Vous pouvez supprimer tous les éléments d'un set des éléments d'un autre set via la fonction suivante. Semblable à la fonction ci-dessus, cela utilise la méthode de métatable __sub pour fournir un raccourci de soustraction de 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({"Pomme", "Citron", "Mangue", "Cerise", "Citron vert", "Pêche"})
local sourFruits = Set.new({"Citron", "Citron vert"})
local sweetFruits = allFruits - sourFruits
sweetFruits:output() -- Mangue, Pomme, Cerise, Pêche