OOP in Lua: Difference between revisions

From Multi Theft Auto: Wiki
Jump to navigation Jump to search
No edit summary
No edit summary
Line 97: Line 97:


When you try to set a value in a table that is not already present, Lua will look for a '''__newindex''' key in the metatable. It's the same sort of situation as '''__index'''; if '''__newindex''' is a table, the key and value will be set in the table specified:
When you try to set a value in a table that is not already present, Lua will look for a '''__newindex''' key in the metatable. It's the same sort of situation as '''__index'''; if '''__newindex''' is a table, the key and value will be set in the table specified:
<syntaxhighlight lang="lua">
other = {}
t = setmetatable({}, { __newindex = other })
t.foo = 3
other.foo -- 3
t.foo -- nil
</syntaxhighlight>
As would be expected, if '''__newindex''' is a function, it will be called with the table, key, and value passed as parameters:
<syntaxhighlight lang="lua">
t = setmetatable({}, {
  __newindex = function(t, key, value)
    if type(value) == "number" then
      rawset(t, key, value * value)
    else
      rawset(t, key, value)
    end
  end
})


t.foo = "foo"
t.bar = 4
t.la = 10
t.foo -- "foo"
t.bar -- 16
t.la -- 100
</syntaxhighlight>
When creating a new key in '''t''', if the value is a number it will be squared, otherwise it will just be set anyway. This introduces us to our friends, '''rawget''' and '''rawset'''.


=== rawget and rawset ===


There are times when you need get and set a table's keys without having Lua do it's thing with metatables. As you might guess, [http://www.lua.org/manual/5.1/manual.html#pdf-rawget rawget] allows you to get the value of a key without Lua using '''__index''', and [http://www.lua.org/manual/5.1/manual.html#pdf-rawset rawset] allows you to set the value of a key without Lua using '''__newindex''' (no these don't provide a speed increase to conventional way of doing things). You'll need to use these when you would otherwise get stuck in an infinite loop. For example, in that last code example, the code '''t[key] = value * value''' would set off the same '''__newindex''' function again, which would get you stuck in an infinite loop. Using '''rawset(t, key, value * value)''' avoids this.


As you probably can see, to use these functions, for parameters we must pass in the target table, the key, and if you're using '''rawset''', the value.


=== Operators ===


Many of the metatable keys available have to do with operators (as in, '''+''', '''-''', etc.), allowing you to make tables support the use of operators on them. For example, say we wanted a table to support the multiplication operator ('''*'''), this is how we would do it:
<syntaxhighlight lang="lua">
t = setmetatable({ 1, 2, 3 }, {
  __mul = function(t, other)
    new = {}
   
    for i = 1, other do
      for _, v in ipairs(t) do table.insert(new, v) end
    end
   
    return new
  end
})


t = t * 2 -- { 1, 2, 3, 1, 2, 3 }
</syntaxhighlight>


This allows us to create a new table with the original replicated a certain amount of times using the multiplication operator. As you can tell, the corresponding key for multiplication is '''__mul'''; unlike '''__index''' and '''__newindex''' the keys for operators can only contain functions. The first parameter these functions always receive is the target table, and then the value on the right hand side (except for the unary - which has the key of '''__unm'''). Here's a list of the supported operators:


* __add: Addition (+)
* __sub: Subtraction (-)
* __mul: Multiplication (*)
* __div: Division (/)
* __mod: Modulos (%)
* __unm: Unary -, used for negation on numbers
* __concat: Concatenation (..)
* __eq: Equality (==)
* __lt: Less than (<)
* __le: Less than or equal to (<=)
(There's only '''==''', '''<''', '''<=''' because you can implement full support for the comparison operators with just those; in fact only '''==''' and '''<''' are needed.)
=== __call ===
Next comes the '''__call''' key, which allows you to call tables as functions. A code example:
<syntaxhighlight lang="lua">
t = setmetatable({}, {
  __call = function(t, a, b, c, whatever)
    return (a + b + c) * whatever
  end
})
t(1, 2, 3, 4) -- 24
</syntaxhighlight>
The function in call is passed the target table as usual, followed by the parameters that we passed in. '''__call''' is very useful for many things. One common thing it's used for is forwarding a call on a table to a function inside that table.
=== __tostring ===
Last of all is '''__tostring'''. If implemented, it's used by [http://www.lua.org/manual/5.1/manual.html#pdf-tostring tostring] to convert a table into a string, most handy when using a function like [http://www.lua.org/manual/5.1/manual.html#pdf-print print]. Normally, when you try to convert a table to a string, you something in the format of "table: 0x<hex-code-here>", but you can change that using '''__tostring'''. An example:
<syntaxhighlight lang="lua">
t = setmetatable({ 1, 2, 3 }, {
  __tostring = function(t)
    sum = 0
    for _, v in pairs(t) do sum = sum + v end
    return "Sum: " .. sum
  end
})
print(t) -- prints out "Sum: 6"
</syntaxhighlight>





Revision as of 03:26, 18 January 2015

This template is no longer in use as it results in poor readability.

Introduction and other related pages

This is a scripting tutorial teaches you the basics about how to start using an Object-Oriented developing interface with Lua.

Glossary

  • environment: either a table or an array containing values.
  • self: predefined variable referring to the environment within which we are executing the code.

Initialising

There is a basic and simple predefined variables we should recognize and know: self.

Our first environment

local array = {}
function array:example (argument)
	return "Hello"
end

What we do upon above is defining a local environment and then declaring the function example as part of it. Alright, so how should we proceed in order to call the mentioned function? As follows:

array:example()
array.example(array, example)

As Lua is so cool, we're able to call a function using two methods: ":" and ".". As you can see on the example above, if we use a dot we're supposed to send self's value to the function. Yes, that's right, and in case we use a colon, self's value will be the environment within which we are executing a code, i.e. array. We can send the self value in case we want a function to override its self and, instead of working as if self was the environment within it's working, it will work over the environment we sent (example under advanced examples).

Variables and further handling

local array = {text = "none"}
function array:setKey (key, value)
	self[key] = value
end
function array:getKey (key)
	return self[key]
end

array:getKey("text") -- returns "none"
array:setKey("text", "something") -- sets "text"'s value to 'something'
array:getKey("text") -- returns "something"

What we do here is retrieving and modifying text's value, which a variable inside array, recurring to functions inside the same environment as the variable is.

Metatables

Content's provenance: NovaFusion


Knowledge of how to use metatables will allow you to be much more powerful in your use of Lua. Every table can have a metatable attached to it. A metatable is a table which, with some certain keys set, can change the behaviour of the table it's attached to. Let's see an example.

t = {} -- our normal table
mt = {} -- our metatable, which contains nothing right now
setmetatable(t, mt) -- sets mt to be t's metatable
getmetatable(t) -- this will return mt

As you can see, getmetatable and setmetatable are the main functions here; I think it's pretty obvious what they do. Of course, in this case we could contract the first three lines into this:

t = setmetatable({}, {})

setmetatable returns its first argument, therefore we can use this shorter form.

Now, what do we put in these metatables? Metatables can contain anything, but they respond to certain keys (which are strings of course) which always start with __ (two underscores in a row), such as __index and __newindex. The values corresponding to these keys will usually be functions or other tables. An example:

t = setmetatable({}, {
  __index = function(t, key)
    if key == "foo" then
      return 0
    else
      return table[key]
    end
  end
})

So as you can see, we assign a function to the __index key. Now let's have a look at what this key is all about.

__index

The most used metatable key is most likely __index; it can contain either a function or table.

When you look up a table with a key, regardless of what the key is (t[4], t.foo, and t["foo"], for example), and a value hasn't been assigned for that key, Lua will look for an __index key in the table's metatable (if it has a metatable). If __index contains a table, Lua will look up the key originally used in the table belonging to __index. This probably sounds confusing, so here's an example:

other = { foo = 3 }
t = setmetatable({}, { __index = other })
t.foo -- 3
t.bar -- nil

If __index contains a function, then it's called, with the table that is being looked up and the key used as parameters. As we saw in the code example above the last one, this allows us to use conditionals on the key, and basically anything else that Lua code can do. Therefore, in that example, if the key was equal to the string "foo" we would return 0, otherwise we look up the table table with the key that was used; this makes t an alias of table that returns 0 when the key "foo" is used.

You may be wondering why the table is passed as a first parameter to the __index function. This comes in handy if you use the same metatable for multiple tables, supporting code re-use and saving computer resources. We'll see an example of this when we take a look at the Vector class.

__newindex

Next in line is __newindex, which is similar to __index. Like __index, it can contain either a function or table.

When you try to set a value in a table that is not already present, Lua will look for a __newindex key in the metatable. It's the same sort of situation as __index; if __newindex is a table, the key and value will be set in the table specified:

other = {}
t = setmetatable({}, { __newindex = other })
t.foo = 3
other.foo -- 3
t.foo -- nil

As would be expected, if __newindex is a function, it will be called with the table, key, and value passed as parameters:

t = setmetatable({}, {
  __newindex = function(t, key, value)
    if type(value) == "number" then
      rawset(t, key, value * value)
    else
      rawset(t, key, value)
    end
  end
})

t.foo = "foo"
t.bar = 4
t.la = 10
t.foo -- "foo"
t.bar -- 16
t.la -- 100

When creating a new key in t, if the value is a number it will be squared, otherwise it will just be set anyway. This introduces us to our friends, rawget and rawset.

rawget and rawset

There are times when you need get and set a table's keys without having Lua do it's thing with metatables. As you might guess, rawget allows you to get the value of a key without Lua using __index, and rawset allows you to set the value of a key without Lua using __newindex (no these don't provide a speed increase to conventional way of doing things). You'll need to use these when you would otherwise get stuck in an infinite loop. For example, in that last code example, the code t[key] = value * value would set off the same __newindex function again, which would get you stuck in an infinite loop. Using rawset(t, key, value * value) avoids this.

As you probably can see, to use these functions, for parameters we must pass in the target table, the key, and if you're using rawset, the value.

Operators

Many of the metatable keys available have to do with operators (as in, +, -, etc.), allowing you to make tables support the use of operators on them. For example, say we wanted a table to support the multiplication operator (*), this is how we would do it:

t = setmetatable({ 1, 2, 3 }, {
  __mul = function(t, other)
    new = {}
    
    for i = 1, other do
      for _, v in ipairs(t) do table.insert(new, v) end
    end
    
    return new
  end
})

t = t * 2 -- { 1, 2, 3, 1, 2, 3 }

This allows us to create a new table with the original replicated a certain amount of times using the multiplication operator. As you can tell, the corresponding key for multiplication is __mul; unlike __index and __newindex the keys for operators can only contain functions. The first parameter these functions always receive is the target table, and then the value on the right hand side (except for the unary - which has the key of __unm). Here's a list of the supported operators:

  • __add: Addition (+)
  • __sub: Subtraction (-)
  • __mul: Multiplication (*)
  • __div: Division (/)
  • __mod: Modulos (%)
  • __unm: Unary -, used for negation on numbers
  • __concat: Concatenation (..)
  • __eq: Equality (==)
  • __lt: Less than (<)
  • __le: Less than or equal to (<=)

(There's only ==, <, <= because you can implement full support for the comparison operators with just those; in fact only == and < are needed.)


__call

Next comes the __call key, which allows you to call tables as functions. A code example:

t = setmetatable({}, {
  __call = function(t, a, b, c, whatever)
    return (a + b + c) * whatever
  end
})

t(1, 2, 3, 4) -- 24

The function in call is passed the target table as usual, followed by the parameters that we passed in. __call is very useful for many things. One common thing it's used for is forwarding a call on a table to a function inside that table.

__tostring

Last of all is __tostring. If implemented, it's used by tostring to convert a table into a string, most handy when using a function like print. Normally, when you try to convert a table to a string, you something in the format of "table: 0x<hex-code-here>", but you can change that using __tostring. An example:

t = setmetatable({ 1, 2, 3 }, {
  __tostring = function(t)
    sum = 0
    for _, v in pairs(t) do sum = sum + v end
    return "Sum: " .. sum
  end
})

print(t) -- prints out "Sum: 6"




Advanced examples

Overriding self's value:

local array = {text = "none"}
local array2 = {text = "none2"}
function array:setKey (key, value)
	self[key] = value
end
function array:getKey (key)
	return self[key]
end
array.getKey(array2, "text") -- returns "none2"
array.setKey(array2, "text", "something2") -- sets array2's "text" value to 'something2'
array.getKey(array2, "text") -- returns "something"
array:getKey("text") -- returns "none"
array.getKey(array, "text") -- same as above

A simple backpacks example:

local backpack = {list = {}}
function backpack:create (owner, slots)
	if self.list[owner] then return end -- return false in case this player already owns a backpack
		local new = {
		items = {},
		slots = slots or 100,
		owner = owner
	}
	setmetatable(new.items, 
	{
		__newindex = function(table, key, value) -- this is called once a new value/item is added into the player's table/backpack
			if #table >= new.slots then return end -- return false in case there isn't any free slot left
			return rawset(table, key, value)
		end
	}
	)
	self.list[owner] = new
end

function backpack:addItem (player, item, value)
	if not self.list[player] then return end
	self.list[player][item] = value
end