As an exercise, I am going to try to implement an object-oriented solution to the homework from the SLua class of April 16, 2025. Let’s start with some introduction to objects.
Revised: 2025-04-22 21:12
In programming, an object is a thing that can contain some data, and that knows some functions that it can perform on that data. The data items are called “member variables”, or “attributes”, and the functions are called “methods”.
In most languages that support objects, there is the notion of a “class”. A class is just a datatype, like “integer” or “string”, except that we can create our own. I will give classes an upper case name, to help distinguish them from other variables that we might have.
Imagine that we wanted to have a new kind of data, representing an avatar, a person, in Second Life. Each person would have the same data elements, such as their key or UUID, and could provide related information like their user name or display name, their age … really anything we wanted to know about.
In Second Life, a user / avatar / person is defined by their unique key, or UUID. So if my UUID is “12345” and Dizzi’s is “67890”, we could create two “instances” of Person like this:
local janet = Person:new("12335")
local dizzi = Person:new("67890")
This is really just like something you already know. We could define two strings like this:
local janet_key = "12345"
local dizzi_key = "67890"
We would have two instances of string, in two variables, with two different values. And we could create two tables:
local janet_table = { uuid="12345"}
local dizzi_table = { uuid="67890"}
Here we have two tables, one with a key-value pair “uuid”:”12345”, and one with “uuid”:”67890”.
Given one of these tables, we could write a function to look up the associated display name of the avatar, like this:
function display_name(person_table)
return ll.GetDisplayName(person_table.uuid)
end
We get the uuid from the person_table and call ll.GetDisplayName
for the desired value.
With objects, it’s a bit different. They add a bit of complexity but they also make many things much easier, as we’ll see below, if all this works out as I expect. If it doesn’t, I’ll erase these articles as if they never happened, so if you are reading this, everything is OK so far.
Let’s talk about what we say to use objects, and hold off some details until later.
We define a janet_object and dizzi_object, of class Person, like this:
local janet_object = Person:new("12345")
local dizzi_object = Person:new("67890")
And we are going to provide a method display_name
so that we can do this:
local janet_object = Person:new("12345")
local dizzi_object = Person:new("67890")
print(janet_object:display_name())
print(dizzi_object:display_name())
And expect that to type something like
Janet Rossini
Dizzi Sternberg
We would define that method like this:
function Person:display_name()
return ll.GetDisplayName(self.uuid)
end
The internal magic of objects, which we’ll explore more later, causes self
to refer to whichever object we used in the call, that is, janet_object
in the first call, and dizzi_object
in the second. So the print gets my name on the first call and Dizzi’s on the second.
When we have this working, Person:new(uuid)
can be used to create a new instance of Person class, and janet_object:displayName()
can be used to return the display name of the object stored in the variable janet_object
.
Let’s begin to look at how it is done. Go with it … we won’t say too much more about the secrets under the covers just yet.
Here is my starting code, a small experiment to see if I could make objects work. Reading it will raise questions. Please try mostly to take in what the code is and not worry about the odd bits. We’ll cover the important matters. For now, just see what you can take in.
-- OO Scanner
-- JR 20250421
-- attempt at a object oriented solution to the SLua homework.
local Person = {}
function Person:new(uuid)
local obj = {_uuid=uuid}
-- ignore the following magical code for now
setmetatable(obj, self)
self.__index = self
-- stop ignoring now
return obj
end
function Person:uuid()
return self._uuid
end
function Person:display_name()
return ll.GetDisplayName(self._uuid)
end
function touch_start(total_number)
local uuid = ll.GetOwner()
local janet_object = Person:new(uuid)
print(`my uuid is {janet_object:uuid()}`)
print(`my name is {janet_object:display_name()}`)
local other_uuid = 'a47bba4a-d54d-4632-8102-baa2ea9fa463'
local other_person_object = Person:new(other_uuid)
print(`their uuid is {other_person_object:uuid()}`)
print(`their name is {other_person_object:display_name()}`)
end
Again: we’ll cover the details a bit as we go on, but mostly I’m asking you to take this code “on faith”. We’ll describe what it does, before fully understanding how it does it. The what is quite nice, and the how gets fairly deep into Lua secrets, which we can generally ignore.
Let’s start with the touch_start
. We have two UUIDs, mine from ll.GetOwner()
, and one that I fetched manually. The touch_start
code creates two objects, me
and them
, passing each new
function one of the UUIDs. Then we print the results of calling two functions on each object, the uuid()
function and the display_name
function.
When I touch the object, it says this, privately to me, because print
is like ll.OwnerSay
:
[10:23] OO Scanner: my uuid is 22ba38c9-06e9-4d42-8a6d-495e35bc33b7
[10:23] OO Scanner: my name is Janet Rossini
[10:23] OO Scanner: their uuid is a47bba4a-d54d-4632-8102-baa2ea9fa463
[10:23] OO Scanner: their name is Marie
Going thru the touch_start
bit by bit:
local my_uuid = ll.GetOwner()
local janet_object = Person:new(my_uuid)
Of course, the first line sets my_uuid
to, um, my uuid. The second line creates a new object, of type Person, with that UUID, stored in janet_object
. The next two lines do similarly:
local their_uuid = 'a47bba4a-d54d-4632-8102-baa2ea9fa463'
local other_person_object = Person:new(their_uuid)
We create a second “instance” of type Person, stored in them
. Then these four lines print the output shown above:
print(`my uuid is {janet_object:uuid()}`)
print(`my name is {janet_object:display_name()}`)
print(`their uuid is {other_person_object:uuid()}`)
print(`their name is {other_person_object:display_name()}`)
So what are these phrases like janet_object:uuid()
? As we talked about above, we know that saying uuid()
would call a function named uuid
. Saying janet_object:uuid()
calls a function named uuid
that is part of the Person object, and furthermore passes the specific instance object, janet_object
to that function. Inside that function, the word self
will refer to the specific calling object, in this case, janet_object
.
Now let’s look at our function Person:new
again:
function Person:new(uuid)
local obj = {_uuid=uuid}
setmetatable(obj, self)
self.__index = self
return obj
end
This gives the class Person a method (function) named new
. We will take it on faith that it does the right things:
The new(uuid)
function creates a new dictionary table, obj
and puts one pair into it _uuid=uuid
, which means that obj
now contains one pair, with key “_uuid and value whatever UUID was passed in. We use the form _uuid
to name a “private” variable in the object. It’s not really private, but we signal that it is intended only for private use with the leading underbar on the name.
The next two lines, setmetatable
and self.__index
, are just Lua magic. Normally we would not even need to write them but today,in SLua, we do. Trust me here, it’s just boilerplate to make objects work. Maybe think of it like this:
function Person:new(uuid)
local obj = {_uuid=uuid}
-- magic boilerplate here
return obj
end
Finally, at the end of new
, we return the new table obj
, as our new instance of Person. This is what goes into janet_object
in the one case in touch_start
and another one into other+_person_object
in the second case.
Now let’s look at the other two methods and we’ll be ready to do some actual work.
function Person:uuid()
return self._uuid
end
The uuid
method just returns the value of the private member _uuid
, that is, the UUID of the person represented. Similarly:
function Person:display_name()
return ll.GetDisplayName(self._uuid)
end
The display_name
method returns the user’s display name by calling the ll.GetDisplayName
function, using the object’s _uuid
member. We feel free to use the private member, because we are implementing a method of Person, though we could have said this instead:
function Person:display_name()
return ll.GetDisplayName(self:uuid())
end
Note that we’d use the colon in that case, because all our methods need the reference self
so that they’re talking to the right object. When you forget and use the dot to try to call a method, you’ll get a message like this:
lua_script:15: attempt to index nil with '_uuid'
lua_script:15 function uuid
I mention it because I tend to forget or mistype often, so the message appears fairly often and I want to remember what it probably means.
The homework problem we were given asked us to change a moderately large SLua program to use less LSL-style code, and more Lua style. It was a tedious task and difficult to get an answer one really liked. Here, I’ll explore how I might do it with objects.
The basic problem is:
When the object is touched, it should scan for avatars, and produce an email report listing details for each avatar detected.
There are lots of details in the sample, most of which can be obtained with direct ll
calls, but a couple that require use of ll.RequestAgentData
, which requires a trip through the dataserver. For our next steps, I propose to print the report, rather than email it, and I’ll just do a few direct calls. Then I’ll do one or two of the indirect dataserver ones.
But first …
Let’s digress for a moment to talk about loops. If you are familiar with Lua’s for
, ipairs
and pairs
, skip to the end of the digression.
Given a Lua array like this:
local squares = {1, 4, 9, 16, 25}
We can print the index of each item and the item itself with this loop:
for i, square in ipairs(squares) do
print(i, square)
end
What is ipairs
? It’s a function called an “iterator” that returns the index and value of each element of the table, squares
in this case. The two names after for
name the index and the value. I chose i
and square
but could have chosen anything. The print statement will be repeated for each index-square pair in squares
, resulting in a print like this:
1 1
2 4
3 9
4 16
5 25
There is another iterator function, pairs
, that is used to iterate tables that are not arrays but key-value tables. We won’t say much about those unless we run across the need for one.
One more tiny thing. If we’re going to ignore the index, which we often do, our convention is to name it _
or perhaps _i
, signifying that we don’t use it. The usual Lua convention is just to use _, but my personal convention is to give it a name if I feel it will aid comprehension.
OK. Let’s get started with our little scanning app. We’ll do the scan by creating a bunch of instances of Person, and we’ll give the Person class more and more methods to product elements of the report.
First, we’ll change our touch_start
like this. I don’t usually put comments in but this time, to aid in the discussion, I will show them here:
-- a global table to contain the people (Person instances)
local People = {}
-- ...
function touch_start(total_number)
-- clear the table
People = {}
-- get an array of Agent UUIDs
local peopleScan = ll.GetAgentList(AGENT_LIST_PARCEL, {})
-- make a person for each UUID and put them in People
for _i, uuid in ipairs(peopleScan) do
table.insert(People, Person:new(uuid))
end
-- for each person i People, print their display name
for i, person in ipairs(People) do
print(person:display_name())
end
end
Up at the top of the program we define a table People, and redefine it in the touch_start, so that it’ll start out empty. Then we get a list of UUIDs of people in the parcel, using ll.GetAgentList
. So peopleScan
is a Lua array of UUIDs, indexed from 1 to however many we got. We want to iterate over that list, creating a Person instance for each UUID. Then we iterate over the people and print each one’s display name. Let’s look in more detail, in case you’re not yet familiar with Lua loops.
Looking at this block:
for _i, uuid in ipairs(peopleScan) do
table.insert(People, Person:new(uuid))
end
This is the code that creates a Person instance for each UUID in `peopleScan’. The statement
for _i, uuid in ipairs(peopleScan) do
sets up a loop that provides the index (_i) and UUID (uuid) for each UUID in the array peopleScan
. We don’t use the index, so I started its name with underbar to signal that it is ignored. Inside the for
loop, we’ll get each index and each UUID, one at a time. We create a Person and insert them into the People table with:
table.insert(People, Person:new(uuid))
When the for
exits, we have People containing a Person instance for each UUID that we saw, and that Person instance has its attribute \_uuid
set to its UUID.
Then we create the simplest report imaginable, with this loop:
for i, person in ipairs(People) do
print(person:display_name())
end
Here again, we loop, this time over the People and for each person, we print their display_name(). I should have called that index \_i
as well, since it is ignored, but I forgot.
And our report comes out like this:
[11:14] OO Scanner: Marie
[11:14] OO Scanner: SungManitu
[11:14] OO Scanner: SungAli
[11:14] OO Scanner: Janet Rossini
(For more about ipairs
, you might find this article useful.)
Already we can begin to see how this might shape up into a report: We’d just print more stuff, except for those pesky dataserver items
We’ll address all that next time. This is probably a lot to take in but look at how small and simple the code is so far:
-- OO Scanner
-- JR 20250421
-- attempt at a object oriented solution to the SLua homework.
local People = {}
local Person = {}
function Person:new(uuid)
local obj = {_uuid=uuid}
setmetatable(obj, self)
self.__index = self
return obj
end
function Person:uuid()
return self._uuid
end
function Person:display_name()
return ll.GetDisplayName(self._uuid)
end
function touch_start(total_number)
People = {}
local peopleScan = ll.GetAgentList(AGENT_LIST_PARCEL, {})
for _i, uuid in ipairs(peopleScan) do
table.insert(People, Person:new(uuid))
end
for i, person in ipairs(People) do
print(person:display_name())
end
end
We define our class Person with methods new
, uuid
, and display_name
. In touch_start
, we make a list of persons, called People, and then, for each person, print out our very brief report.
I hope you can see that the extra work in setting up the class makes the reporting part easier. I think we’ll see that that carries through right along, although the dataserver thing will toss us a bit of a curve. But not too big of a curve, it’ll just change a bit of code, because we can’t do the report until the dataserver calls are done. Don’t worry, it’ll all work out!
Next time, we’ll improve the report and, probably, deal with the dataserver issue.