Undocumented Feature in llListen()

Don’tcha just love the phrase “undocumented feature”? I think I shall expound upon this term at a later date. Right now, I want to concentrate on a recent experiment of mine.

This week in the CSMS group, a question arose about the consequences of calling llListen() more than once in the same state, changing only the name parameter, without removing the previous listen first. The scripter intended to toggle listening to one person and then the other.

My initial response was (1) that you wouldn’t stop listening to the previous person, and (2) it’s bad form not to remove a listen you don’t want anyway.

From the explanation in the wiki, I had also assumed that, if you don’t bother removing any listens, each new llListen() call would register a new listen callback with a unique handle, resulting in “hearing” one person’s line of chat more than once.

Since I haven’t coded that particular scenario before, I decided to test it out. I began with two scripts for three objects: “George” and “Martha”, who do the talking, and “Florence” who does the listening.

George and Martha get the same script. When they speak, they are identified by their names, which are the names of their objects:
[lsl]
CHANNEL = -1234567;

// ====================================================

default {
state_entry() {
llSay (CHANNEL, "Starting up.") ;
llSetTimerEvent (10);
}

// —————————————————-

timer() {
llSay (CHANNEL, "Hello again.") ;
}
}
[/lsl]
The output, as you can tell, is George or Martha speaking one line of chat every 10 seconds. Here’s George speaking on that channel:

George: Starting up.
George: Hello again.
George: Hello again.
George: Hello again.

…and so on. How Martha’s side of the discussion differs from this is left to the reader as an exercise.

To test my hearing against George and Martha, I’m going to enable them in sequence with each touch to Florence:
[lsl]
integer CHANNEL = -1234567;
integer toggle = 0;
integer handle = 0;

// ====================================================

default {

touch_start (integer numDetected) {

string who = "George" ;
if (toggle) who = "Martha" ;

// A call to llListenRemove() really should go here.
handle = llListen (CHANNEL, who, "", "") ;
llOwnerSay ("Now listening to " + who +
" using handle #" + (string) handle + ".") ;

toggle = (toggle + 1) & 1;
}

// —————————————————-

listen (integer channel, string name, key id,
string message) {
llOwnerSay (name + " says: " + message) ;
}
}
[/lsl]
I should note here that this is a script not intended for production, and I usually isolate myself during tests like these, so I haven’t bothered with a loop calling the llDetected*() functions.

On the first touch to Florence, she begins to listen to George.

Florence: Now listening to George using handle #1.
Florence: George: Hello again.
Florence: George: Hello again.
Florence: George: Hello again.

On the second touch, she also listens to Martha. So far the output is:

Florence: George: Hello again.
Florence: George: Hello again.
Florence: Now listening to Martha using handle #2.
Florence: George: Hello again.
Florence: Martha: Hello again.
Florence: George: Hello again.
Florence: Martha: Hello again.

Then I touch Florence once more. (By this time, Florence should have slapped me in the muzzle, but that’s another matter.) I ask Florence thereby to register another listen to George. I expect to hear:

Florence: George: Hello again.
Florence: Now listening to George using handle #3.
Florence: Martha: Hello again.
Florence: George: Hello again.
Florence: George: Hello again.
Florence: Martha: Hello again.
Florence: George: Hello again.
Florence: George: Hello again.
Florence: Martha: Hello again.

Instead I hear:

Florence: George: Hello again.
Florence: Now listening to George using handle #1.
Florence: Martha: Hello again.
Florence: George: Hello again.
Florence: Martha: Hello again.
Florence: George: Hello again.
Florence: Martha: Hello again.

Clearly the second llListen() request for George has not been recognized. No, wait, that’s not right; the call to listen to George, using the identical parameters as the previous listen to George, has returned the previously registered handle.
I kept touching Florence to no avail:

Florence: George: Hello again.
Florence: Martha: Hello again.
Florence: George: Hello again.
Florence: Now listening to Martha using handle #2.
Florence: Martha: Hello again.
Florence: George: Hello again.
Florence: Martha: Hello again.
Florence: Now listening to George using handle #1.
Florence: George: Hello again.
Florence: Martha: Hello again.
Florence: George: Hello again.
Florence: Martha: Hello again.

I can now say with confidence that previously registered and active calls to llListen() using the same parameters are not re-registered. This would make sense for efficiency; after all, does it really make sense to listen twice to the same object on the same channel?

OK where does it say that in the wiki? I don’t believe it does.

The argument could be made that this has no need to be in the wiki, for you shouldn’t call llListen() repeatedly in this manner without first calling llListenRemove() first. Better still, you can set up one listen request, then move the filter for the object’s name to the event code:
[lsl]
integer CHANNEL = -1234567;
integer toggle = 0;

// ====================================================

default {
state_entry () {
llListen (CHANNEL, "", "", "") ;
llOwnerSay ("Now listening to George.") ;
}

// —————————————————-

touch_start (integer numDetected) {
toggle = (toggle + 1) & 1;
string who = "George" ;
if (toggle) who = "Martha" ;
llOwnerSay ("Now listening to " + who + ".") ;
}

// —————————————————-

listen (integer channel, string name, key id,
string message) {

if ((name == "George") && (toggle == 0))
llOwnerSay ("George says: " + message) ;

else if ((name == "Martha") && (toggle == 1))
llOwnerSay ("Martha says: " + message) ;
}
[/lsl]
And the result is a true listening toggle between objects in the same state:

Florence: Now listening to George.
Florence: George says: Hello again.
Florence: George says: Hello again.
Florence: George says: Hello again.
Florence: Now listening to Martha.
Florence: Martha says: Hello again.
Florence: Martha says: Hello again.
Florence: Now listening to George.
Florence: George says: Hello again.
Florence: George says: Hello again.

Though outside the scope of this blog entry, you could also use different states to control Florence’s selective hearing. That solution, though, works best in very simple programs, or in scripts that have been split off to accommodate a separate user interface. The old adage is true: within every small program, there’s a much larger program trying to get out.

Originally posted 29 Aug 2008
Last edited 30 Aug 2008