I think we have the background now to describe Lua objects and how they work. Let’s try. Let me state our conclusion now and then build up to understanding it:
This article assumes a basic level of knowledge of Second Life scripting. If there’s something here that you’re missing, please get in touch with me in SL so that I can try to improve the article.
An object in SLua consists of a table containing the attributes of the object, with a metatable that defines the object’s operations, called methods.
In what follows, we’re going to bring together the weird technical subjects from the past few articles to show how objects work. This is, in my view, “nice to know”, but if all we want to do is use objects, we do not usually need to deal with these arcana.
An “object”, in programming terms, is not a cube, the kind of object you may be familiar with in Second Life. An object is a sort of smart variable. It consists of attributes or members that are other variables, so that a Vector object might know x, y, and z, and a Person object might know age, height, and weight. But an object can also have behavioral attributes, called methods, that can perform operations on individual objects or even more than one object.
A vector might be able to normalize
itself, returning itself scaled to length one, and you might be able to add two vectors v1 + v2, because vector behavior can include binary operations.
A person’s BMI, Body Mass Index, is the quotient of weight divided by height. Without objects, just a person table containing height and weight, we might script this:
function bmi(person)
return person.weight / person.height^2
end
However, we’ve already seen that we could define our Person object so that we can ask for BMI directly, like this:
person = { name="janet", height=2, weight=54, bmi=function(p)
return p.weight/p.height^2
end}
And then we can ask for BMI like this:
print(`{person.name}'s BMI is {person:bmi()} (metric)`)
And get this:
[05:16] Scratch: janet's BMI is 13.5 (metric)
When we say
person:bmi()
, with a colon, it means the same asperson.bmi(person)
with the dot, so that’s how our bmi function gets its parameter. Generally speaking, we will define our methods using colon instead of the dot we used here, which provides an automaticself
parameter where we have ourp
parameter here. Same-same, just syntactic sugar.
But wait, there’s more. We’ve also seen that tables can have metatables, and the metatables can have metamethods defined in them and one metamethod (really a table) is named __index
. If janet’s table had a metatable that defined bmi
, and that table also had an __index
member pointing to that same table, then janet (and any other person) could find the bmi
function. Like this:
function state_entry()
local meta = {bmi=function(p)
return p.weight / p.height^2
end}
meta.__index = meta
j = { name="janet", height=2, weight=54}
setmetatable(j, meta)
f = { name="froggie", height=1.5, weight=60}
setmetatable(f, meta)
end
Then when we do this:
print(`{j.name} has BMI {j:bmi()} (metric)`)
print(`{f.name} has BMI {f:bmi()} (metric)`)
We get this:
[05:28] Scratch: janet has BMI 13.5 (metric)
[05:28] Scratch: froggie has BMI 26.666666666666668 (metric)
We could put two, ten, any number of methods into the table meta
and janet and froggie and all the other persons would understand them. If we were building a Vector, all our vectors could understand norm
and magnitude
and distance
and all the binary operations, just because there was one table with all those functions defined just once, and an __index
entry in the table, pointing to the table itself.
So in SLua we can say:
An object in SLua consists of a table containing the attributes of the object, with a metatable that defines the object’s operations, called methods.
Attributes, by the way, are often called “members” or “member variables”. All the same thing: the specific values that make up an object, its x, y, and z, or height, age, and weight, or balance and deposits and withdrawals, whatever we choose to define.
Well, no. as things stand now, we would all have to create our own objects and if we were all like me, we would forget to give some of them their metatable and things would not work as well as they might. So we at least need a standard personal way to create objects, and in my view, Linden Labs should provide at least one standard way that we could all use.
Early on in this series, I showed a simple way to create objects, the Person objects we needed for a SLua class assignments. We created a new person, providing their UUID, like this:
local joe = Person:new("232425")
Our new
function was a bit tricky but we only had to do it once:
function Person:new(uuid)
local obj = {_uuid=uuid}
setmetatable(obj, self)
self.__index = self
return obj
end
For simple objects that don’t need “inheritance”, which we may discuss and revile at some later date, this style is nearly adequate, although every time we create a new class, we’d need to put in that obscure setting of the metatable and __index
. But it would be boilerplate, always the same, so not too bad. It would be better, in my view, if we had a function to call where we only had to concern ourselves with the attributes and methods we wanted to create, and none of the boilerplate. It turns out that that is possible and we’ll surely explore that in some future article.
I’d suggest that there are two take-home notions here that are key: