By way of summing up, at least until the next article, today we’ll look at a complete example of a simple SLua class, and a diagram showing where the metatable that makes it work fits in.
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.
In this example, we’ll create a small Person class that knows first and last name, middle initial, height in meters and weight in kilograms. Its methods will produce full name, name in last, first, middle initial form, and will calculate metric BMI.
Here are the tests I wrote for this class. I’ll show you today’s trivial testing framework in the next article.
function run_tests()
test_correct = 0
test_wrong = 0
p1 = Person:new("Janet", "V", "Rossini", 1.78, 55)
p2 = Person:new("Froggie", "T", "Gremlin", 1., 20)
assert_equals(p1:name_full(), "Janet V. Rossini")
assert_equals(p1:name_last_first(), "Rossini, Janet V.")
assert_nearly_equal(p1:bmi(), 17.35, 0.05)
assert_equals(p2:name_full(), "Froggie T. Gremlin")
assert_nearly_equal(p2:bmi(), 20, 0.001)
print(`Tests: correct {test_correct}, wrong {test_wrong}`)
end
When the tests run, the cube says:
[05:43] Example Class: Tests: correct 5, wrong 0
So that’s good. We see in the code above that we create a new Person instance with Person:new
, listing all the values for the members of the object. We call the various functions on instance p1 by saying p1:name_full()
and so on. Recall that using the : notation passes p1 into the method as the first parameter, named self
inside the method. We defined, Person:name_full
like this:
function Person:name_full()
return `{self.first} {self.mi}. {self.last}`
end
Simple enough, just returns the interpolated string in pretty much the obvious way. The other two methods are defined similarly:
function Person:name_last_first()
return `{self.last}, {self.first} {self.mi}.`
end
function Person:bmi()
return self.weight_kg / self.height_m^2
end
Now for the meat of this meal, Person:new
. We’ve seen a very similar example before and earlier articles have described the metatable mechanism that makes this work but we’re here to put a nice ribbon around all the ideas so far. Here’s Person: new
.
local Person = {}
function Person:new(first, mi, last, height, weight)
obj = {}
self.__index = self
setmetatable(obj, self)
obj.first = first
obj.mi = mi
obj.last = last
obj.height_m = height
obj.weight_kg = weight
return obj
end
I’ll ask you to skip over the self.__index
and setmetatable
for a moment, and notice that mostly what this function does is create a table with our field names (attributes, member variables, all the same idea) set to the values provided in the call to new
, and then we return that table.
So that table, for Froggie, will be no more and no less than this:
{
name="Froggie",
mi="T",
last="Gremlin",
height_m = 1.0,
weight_kg = 20
}
That object knows all of Froggie’s member variables … but it doesn’t know any of Froggie’s methods. That’s the job of these two lines:
self.__index = self
setmetatable(obj, self)
When we call Person:new
, self
is Person, the original empty table shown above. So we set that table to be
Person = {
__index=Person
}
We vaguely remember that in a metatable, __index
names the table to search for missing keys in the table that has that metatable. So if Person were a metatable, the table that uses it as a metatable will look in Person for missing key … like the missing methods name_last()
and so on. And sure enough, the next line sets the metatable for our new instance, obj
to self
, which is Person.
Remember that we already put all our methods into Person, with function Person:bmi()
and so on. So this diagram shows the present situation:
So now, when either of our instances is asked for, say, name_last_first
, Lua finds that the instance’s own table does not include the key “name_last_first”. So there is what the insiders call an “event” and Lua looks in the instance’s metatable for __index
. It finds a table of that name, namely Person, (the metatable again) and looks there to try to find “name_last_first”. It finds the key, grabs its value, the function, and calls it, passing our instance as self
.
And voila, our instance understands the methods of the class! Isn’t that neat? I think it is.
I promised to show you how those tests worked. I’ll do that in the next article. Check it out!