Janet's Notes

Miscellaneous Articles Trying to be Useful


Project maintained by JanetRossini

OO #4, Second Dataserver

Let’s put in the second dataserver-requiring field, the pay info. It will work just like the one for rez date. We’ll proceed in tiny steps. Each step, we’ll try to put in the code that best deserves it.

We need a new query list, new handler for that event, and some new additions in the line that prints the information and support for that information in the Person class. It will all be just the same as what we’ve done before.

First, we’ll add another query list up top:

local RezDayQueries = {}
local PayInfoQueries = {}

local People = {}
local Person = {}

We’ll initialize it with the other items when we scan, and we’ll look at the resulting code and think about how to call it:


function make_people_list()
   People = {}
   RezDayQueries = {}
   PayInfoQueries = {}  -- <---
   local peopleScan = ll.GetAgentList(AGENT_LIST_PARCEL, {})
   for _i, uuid in ipairs(peopleScan) do
      local person = Person:new(uuid)
      table.insert(People, person)
      RezDayQueries[person:requestRezDate()] = person
      -- what should we do here?
   end
end

Well, it seems we need a new method on person, requestPayInfo. Let’s assume that we have such a method and call it, to finish up this function:

function make_people_list()
   People = {}
   RezDayQueries = {}
   PayInfoQueries = {}
   local peopleScan = ll.GetAgentList(AGENT_LIST_PARCEL, {})
   for _i, uuid in ipairs(peopleScan) do
      local person = Person:new(uuid)
      table.insert(People, person)
      RezDayQueries[person:requestRezDate()] = person
      PayInfoQueries[person:requestPayInfo()] = person
   end
end

Now, before we forget, we’ll do requestPayInfo to be like requestRezDate:

function requestPayInfo()
   return ll.RequestAgentData(self._uuid, DATA_PAYINFO)
end

function Person:requestRezDate()
   return ll.RequestAgentData(self._uuid, DATA_BORN)
end

At this point the program should run as before, so I’ll run it to be sure it does. I’m glad I did, because I got this message:

lua_script:63: attempt to call missing method 'requestPayInfo' of table
lua_script:63 function make_people_list
lua_script:100 function touch_start

As you can easily see, I forgot to put Person: on the new method. It should be:

function Person:requestPayInfo()
   return ll.RequestAgentData(self._uuid, DATA_PAYINFO)
end

Well, it works and it doesn’t. It prints the report several times, once for every avatar present. Why? Because we fire the report from dataserver, when the one queue is empty:

function dataserver(queryId, data)
   local person = RezDayQueries[queryId]
   if person then
      person._rez_date = data
   end
   RezDayQueries[queryId] = nil

   if next(RezDayQueries) == nil then
      create_report()
   end
end

I did not see that coming. That’s why I test frequently: I don’t have to think so hard. The test run tells me what I have to do next. And what I’ll do now is replicate the RezDate code to deal similarly with PayInfo:


function dataserver(queryId, data)
   local person = RezDayQueries[queryId]
   if person then
      person._rez_date = data
   end
   RezDayQueries[queryId] = nil

   local person = PayInfoQueries[queryId]
   if person then
      person._pay_info = data
   end
   PayInfoQueries[queryId] = nil

   if next(RezDayQueries) == nil and next(PayInfoQueries) == nil then
      create_report()
   end
end

Test again, thinking it should work now. I get the right number of paragraphs now. Let’s see where the pay info is supposed to be displayed and put it there. It goes right after the rez date, it turns out.

function line_2(person)
   return `Shape: {person:shape()}, Language: {'not implemented'}, RezDate: {person:rez_date()} Pay Info: {person:pay_info()}`
end

Unlike Python, we can’t fold long strings with just enter, so let’s do this:

function line_2(person)
   return `Shape: {person:shape()}, Language: {'not implemented'}, ` .. 
          `RezDate: {person:rez_date()} Pay Info: {person:pay_info()}`
end

That’s a bit more readable. The report looks good. Here’s me:

[07:08] OO Scanner: 22ba38c9-06e9-4d42-8a6d-495e35bc33b7, janet.rossini, Janet Rossini
Shape: 0, Language: not implemented, RezDate: 2007-02-24 Pay Info: 3
Position: <155.39104, 32.56980, 23.10590>
Hover: 0

I don’t know what Pay Info 3 means but there it is. Ah it is a two bit field and 3 means pay info is on file and has been used. Whee!.

Let’s review the morning’s work so far.

Review

Because we have all the different kinds of functionality separated neatly, all our changes were just a couple of lines at a time. I was trained and have learned to work that way, and I find it very productive, because after only a couple of lines I can test and make sure I’m on track. Other people who are not used to working in tiny bites might prefer to write larger chunks of code, and if that works for you, well, do it. But I like the tiny steps. This time it was:

  1. Add a new query list;
  2. Initialize it;
  3. Call a new method that we hadn’t written yet, requestPayInfo. (I just noticed that I used camelCase for those two methods and snake_case for everything else. I’ll change them right now. Done, tested.)
  4. Implement the new method to return the private member _pay_info that our request will set up.
  5. Add the code to dataserver to check for the new query coming back and pass the _pay_info1 to the right Person.
  6. Add the additional data item to the correct line in the paragraph.

After each of those steps the program was testable, and in every case it worked as advertised, except that before we checked for the query it repeated the output once for each new dataserver event, because we were not yet consuming them and not yet checking for both queues empty before printing. Because I was able to test after every tiny step, I caught that issue immediately.

I think this is a good place to stop. Let me sum up.

Summary

The basic shell of this program was created very early on, first just printing one item, then printing all four lines of the paragraph, mostly with no data yet. Everything we’ve done since was done in very tiny steps, always working.

Our design has a Person class, enabling us to create a person instance for each UUID we get from the scan, and that instance acts as a container for all the information we need to report. It happens to compute that information on demand, except for the dataserver information, which it requests and then our dataserver code fills in the details.

Describing this gives me what I think is a good idea for another session. What if we didn’t put code in dataserver that knows so much about Person, but instead let the Person handle the dataserver event?

Anyway, it should be easy to see that filling in the rest of the fields just comes down to some more person methods, each one calling ll.GetObjectDetails for its bit of information.

Once again, I’ll put the whole program here at the bottom of the article in case you want to see it.

Bye for now!


-- OO Scanner
-- JR 20250423

local RezDayQueries = {}
local PayInfoQueries = {}

local People = {}
local Person = {}

function Person:new(uuid)
   local obj = {_uuid=uuid, _rez_date="dataserver"}
   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 Person:hover()
   return ll.GetObjectDetails(self._uuid, {OBJECT_HOVER_HEIGHT})[1]
end

function Person:pay_info()
   return self._pay_info
end

function Person:position()
   return ll.GetObjectDetails(self._uuid, {OBJECT_POS})[1]
end

function Person:request_pay_info()
   return ll.RequestAgentData(self._uuid, DATA_PAYINFO)
end

function Person:request_rez_date()
   return ll.RequestAgentData(self._uuid, DATA_BORN)
end

function Person:rez_date()
   return self._rez_date
end

function Person:shape()
   return ll.GetObjectDetails(self._uuid, {OBJECT_BODY_SHAPE_TYPE})[1]
end

function Person:user_name()
   return ll.GetUsername(self._uuid)
end

function make_people_list()
   People = {}
   RezDayQueries = {}
   PayInfoQueries = {}
   local peopleScan = ll.GetAgentList(AGENT_LIST_PARCEL, {})
   for _i, uuid in ipairs(peopleScan) do
      local person = Person:new(uuid)
      table.insert(People, person)
      RezDayQueries[person:request_rez_date()] = person
      PayInfoQueries[person:request_pay_info()] = person
   end
end

function create_report()
   for i, person in ipairs(People) do
      local paragraph = create_paragraph(person)
      print(paragraph)
   end
end

function create_paragraph(person)
   local lines = {
      line_1(person), line_2(person), line_3(person), line_4(person)
   }
   return table.concat(lines, "\n")
end

function line_1(person)
   return `{person:uuid()}, {person:user_name()}, {person:display_name()}`
end

function line_2(person)
   return `Shape: {person:shape()}, Language: {'not implemented'}, ` .. 
          `RezDate: {person:rez_date()} Pay Info: {person:pay_info()}`
end

function line_3(person)
   return `Position: {person:position()}`
end

function line_4(person)
   return `Hover: {person:hover()}`
end

-- Primary Events

function touch_start(total_number)
   make_people_list()
end

function dataserver(queryId, data)
   local person = RezDayQueries[queryId]
   if person then
      person._rez_date = data
   end
   RezDayQueries[queryId] = nil

   local person = PayInfoQueries[queryId]
   if person then
      person._pay_info = data
   end
   PayInfoQueries[queryId] = nil

   if next(RezDayQueries) == nil and next(PayInfoQueries) == nil then
      create_report()
   end
end