Janet's Notes

Miscellaneous Articles Trying to be Useful


Project maintained by JanetRossini

OO Homework Solution

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

Introduction

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.

The Status So Far …

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.

The First Experiment

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 Problem

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 …

Digression

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.

End Digression

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.