Janet's Notes

Miscellaneous Articles Trying to be Useful


Project maintained by JanetRossini

OO #3, Dataserver

The tricky bit in the homework is the dataserver bits. Let’s get started on that part.

Revised 2025-04-22 21:28

Let’s go ahead and put in one of the fields that needs to use the dataserver. We’ll follow the scheme that was in the homework assignment. It works like this:

We actually have two fields that we’ll ultimately need, the rez date and the payment info. So we’ll make two Lua tables, one for each. The tables will be in dictionary form, with the key being the key that comes back from the call to ll.RequestAgentData, and the value being the Person instance for whom we are requesting the information.

We’ll request the data in the new method, I think. No, on second thought, we’ll have a method for doing that and we’ll call it directly.

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

We’ll call that method and enqueue the query, in the scanning code. That starts out like this:

function make_people_list()
   People = {}
   local peopleScan = ll.GetAgentList(AGENT_LIST_PARCEL, {})
   for _i, uuid in ipairs(peopleScan) do
      table.insert(People, Person:new(uuid))
   end
end

We’ll extract a variable person and use it, like this:

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

I think I’ll add a dataserver event now, with a print in it:

function dataserver(queryId, data)
   local person = RezDayQueries[queryId]
   if person then
      print(`RezDay for {person:display_name()} is {data}`)
   end
   RezDayQueries[queryId] = nil
end

We use the queryId to fetch the person from the table. If we get a non-nil person, we print. In any case, we remove the queryId from the list. Should we print something if we don’t recognize the Id? Perhaps, but so far I’ve done what I’ve done. Let’s see what it takes to make this work. Curiously enough, it works right off:

[10:08] OO Scanner: 22ba38c9-06e9-4d42-8a6d-495e35bc33b7, janet.rossini, Janet Rossini
Shape: 0, Language: not implemented, RezDate: dataserver
Position: <155.63109, 32.43759, 23.10590>
Hover: 0
[10:08] OO Scanner: RezDay for Marie is 2022-07-13
[10:08] OO Scanner: RezDay for SungAli is 2012-07-14
[10:08] OO Scanner: RezDay for Janet Rossini is 2007-02-24

Naturally, the dataserver events take place after we’ve done all the printing of the report, which we currently do in touch_start:

function touch_start(total_number)
   make_people_list()
   create_report()
end

What we need to do now is to kick off the report when we are out of dataserver events. Since we remove them when we see them, we should do the report then. We’ll kick it off from dataserver, like this:

function touch_start(total_number)
   make_people_list()
end

function dataserver(queryId, data)
   local person = RezDayQueries[queryId]
   if person then
      print(`RezDay for {person:display_name()} is {data}`)
   end
   RezDayQueries[queryId] = nil

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

Now we should see the dataserver prints before the reports instead of after. And we do:

[10:14] OO Scanner: RezDay for Marie is 2022-07-13
[10:14] OO Scanner: RezDay for SungAli is 2012-07-14
[10:14] OO Scanner: RezDay for Janet Rossini is 2007-02-24
[10:14] OO Scanner: a47bba4a-d54d-4632-8102-baa2ea9fa463, marieomani, Marie
Shape: 0, Language: not implemented, RezDate: dataserver
Position: <172.95132, 27.90528, 22.98849>
Hover: 0.125
[10:14] OO Scanner: ebff743c-5e47-400a-8ea6-a3bdb417b457, sungali, SungAli
Shape: 0, Language: not implemented, RezDate: dataserver
Position: <160.34364, 41.51177, 22.65000>
Hover: 0
[10:14] OO Scanner: 22ba38c9-06e9-4d42-8a6d-495e35bc33b7, janet.rossini, Janet Rossini
Shape: 0, Language: not implemented, RezDate: dataserver
Position: <155.63109, 32.43759, 23.10590>
Hover: 0

Now we just need to give the rez day to the person. We do that right inside the dataserver event, where we are currently doing the print. We remove the print and get this:

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

Now we should get the correct entry right in the report, because we set up to use it last time. And sure enough, we do! Here’s my report as of now:

[10:17] OO Scanner: 22ba38c9-06e9-4d42-8a6d-495e35bc33b7, janet.rossini, Janet Rossini
Shape: 0, Language: not implemented, RezDate: 2007-02-24
Position: <155.63109, 32.43759, 23.10590>
Hover: 0

This is a good spot to review and sum up. Let’s do.

Review

We made a space in the Person object for the rez date, _rez_date and initialized it to “dataserver”, right in Person:new():

function Person:new(uuid)
   local obj = {_uuid=uuid, _rez_date="dataserver"}
   setmetatable(obj, self)
   self.__index = self
   return obj
end

We used that field (member, attribute) in our report line, initially just showing “dataserver”, which showed us that we were referencing the right field:

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

We then built a query table, indexed by queryId, returning a Person, issuing the query with a direct call to the Person as we scan them in:

local RezDayQueries = {}


function make_people_list()
   People = {}
   RezDayQueries = {} -- <---
   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 -- <---
   end
end

Then,in the dataserver event, we look up the query in our table, find the person it pertains to, set that person’s _rez_date, and remove the query from the query table. If there are no more queries, we have them all and we’re ready to kick off the report:

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

The cryptic next(RezDayQueries) == nil is the Lua trick for asking “is this table empty”. I borrowed it from the homework, as I did the idea of having the query table point to the person. I think I’ll research whether there is a less cryptic way of testing for table empty.

Summary

With just a few lines, and very little digging around, we’ve installed a dataserver event for rez date. Next time we can do the same trick for the payment info, using a second query table for convenience, and fill in the rest of the fields by calling ll.GetObjectDetails(). All of that will be pretty much by rote, because we’ve now got solutions to all the issues in the problem. The rest is just filling things in.

There are certainly things to improve. One is the access to the private member of Person that’s in dataserver. It’s certainly OK in principle. That is, it works. It’s not best practice, however, and a perfectionist would either remove the private-indicating underbar, or write a setter method on the object. Next time, if I remember, we’ll do that just to show how it’s done.

I’ll dump the whole program below, so you can see everything that’s there. What I hope you’ll notice is how everything is separated out into related functions, and how simple (although repetitive) the solution has become.

Until next time!


-- OO Scanner
-- JR 20250421

-- Global Collections

local RezDayQueries = {}
local People = {}

-- Person Class Definition

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:position()
   return ll.GetObjectDetails(self._uuid, {OBJECT_POS})[1]
end

function Person:requestRezDate()
   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

-- Reporting (should we make this an object also?)

function make_people_list()
   People = {}
   RezDayQueries = {}
   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
   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()}`
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

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