Welcome to the Second Life Forums Archive

These forums are CLOSED. Please visit the new forums HERE

Preventing Listener Orphaning and Getting Your Candy

diamond Marchant
Registered User
Join date: 16 Jun 2009
Posts: 4
10-19-2009 12:34
Being Halloween season, there are lots of candy bowls about. It occurred to me that if these bowls use the method of listener management often given in examples (e.g. in the Scripting Your World book), that is, where gListenHandle is an integer, then there is a chance of orphaning listeners (not removing them) and/or dialogs not functioning when clicked. Here is an example of a bad listener.

From: someone

// (DS) Bad Listener
// created by diamond Marchant, Diamond Synthesis
// abbynormal brain... do not use

// failure modes
// 1. if the same agent touches twice, clicks OK to both, a listener is orphaned. Both dialogs give candy.
// 2. if agent 1 touches, agent 2 touches, agent 2 clicks OK, agent 1 clicks OK, a listener is orphaned. Both agents get candy.
// 3. if agent 1 touches, agent 2 touches, agent 1 clicks OK, agent 2 clicks OK, a listener is orphaned. Only agent 1 gets candy.

integer TIMEOUT = 60;
integer STOP_TIMER = 0;
integer gListenHandle;

integer gCount = 0;

integer randomChannel() {
return (integer)llFrand(-200000) - 100;
}

showDialog(key id, string text, list buttons) {
integer cmdChannel = randomChannel();
llDialog(id, text, buttons, cmdChannel);
// only keep track of last listener
gListenHandle = llListen(cmdChannel, "", id, "";);
llSetTimerEvent(TIMEOUT);
}
cleanupListenHandle() {
llListenRemove(gListenHandle);
llSetTimerEvent(STOP_TIMER);
}
default {
on_rez(integer param)
{
llResetScript();
}
state_entry()
{
}
touch_start(integer total_number)
{
showDialog(llDetectedKey(0), "Candy?",[]);
}
listen(integer channel, string name, key id, string message)
{
cleanupListenHandle();
gCount++;
if (message == "OK";)
llWhisper(0, "I would give "+llKey2Name(id)+" some if I had any ("+(string) gCount+";)";);
}
timer() {
llWhisper(0,"Listener Timed Out";);
cleanupListenHandle();
}
}
// end script


The situation is improved by making gListenHandle into a list and keeping track of listeners by key. Here is an improved candy bowl. Making it actually give candy is an exercise left to the reader. Enjoy!

Note: This post has been edited to correct a cmdChannel bug. If you are only going to keep one listener per agent, you need to use the same cmdChannel for all Dialogs for that agent. Otherwise, if the agent clicks Ignore, then opens another Dialog on a new (random) cmdChannel, the original Listener will not hear it.

From: someone

// (DS) Better Listener
// created by diamond Marchant, Diamond Synthesis
// use if you like

// desired behavior
// 1. if the same agent touches twice, clicks OK to both, the only listener is removed. Only one dialog gives candy.
// 2. if agent 1 touches, agent 2 touches, agent 2 clicks OK, agent 1 clicks OK, both listeners are removed. Both agents get candy.
// 3. if agent 1 touches, agent 2 touches, agent 1 clicks OK, agent 2 clicks OK, both listeners are removed. Both agents get candy.

integer TIMEOUT = 60;
integer STOP_TIMER = 0;
list gListenHandles; // list holds one listen handle per agent

integer gCount = 0;

integer randomChannel() {
return (integer)llFrand(-200000) - 100;
}

showDialog(key id, string text, list buttons) {
integer cmdChannel;
integer i = llListFindList(gListenHandles, [id]);
if (i == -1)
cmdChannel = randomChannel();
else
// keep using same cmdChannel if this agent already has a listener
cmdChannel = llList2Integer(gListenHandles, i+2);
llDialog(id, text, buttons, cmdChannel);
if (i == -1)
// only add a listener for this agent if one does not exist
gListenHandles += [ id, llListen(cmdChannel, "", id, "";), cmdChannel ];
llSetTimerEvent(TIMEOUT);
}
cleanupListenHandle(key id) {
if (id == NULL_KEY) {
// on timeout, remove all listeners
integer i;
integer n = llGetListLength(gListenHandles);
for (i=0; i < n; i+=3) { // list stride length is 3
llListenRemove(llList2Integer(gListenHandles, i+1));
}
gListenHandles = [];
llSetTimerEvent(STOP_TIMER);
}
else {
// remove the listener for this agent
integer i = llListFindList(gListenHandles, [id]);
if (i != -1) {
llListenRemove(llList2Integer(gListenHandles, i+1));
gListenHandles= llDeleteSubList(gListenHandles, i, i+2);
}
if (llGetListLength(gListenHandles) ==0)
llSetTimerEvent(STOP_TIMER);
}
}
default {
state_entry()
{
}
touch_start(integer total_number)
{
integer i;
// handle multiple touches, sim may be laggy
for (i=0;i < total_number;i++) {
showDialog(llDetectedKey(i), "Candy?",[]);
}
}
listen(integer channel, string name, key id, string message)
{
cleanupListenHandle(id);
gCount++;
if (message == "OK";)
llWhisper(0, "I would give "+llKey2Name(id)+" some if I had any ("+(string) gCount+";)";);
}
timer() {
llWhisper(0,"Listener Timed Out";);
cleanupListenHandle(NULL_KEY);
}
}

diamond Marchant
Registered User
Join date: 16 Jun 2009
Posts: 4
10-21-2009 06:53
The original post has been edited to correct a cmdChannel bug. If you are only going to keep one listener per agent, you need to use the same cmdChannel for all Dialogs for that agent. Otherwise, if the agent clicks Ignore, then opens another Dialog on a new (random) cmdChannel, the original Listener will not hear it.
Innula Zenovka
Registered User
Join date: 20 Jun 2007
Posts: 1,825
10-21-2009 09:29
Hmm. I normally do something like this for dialogs. I'm not saying it's better than your way, but what's wrong with it for something like a candy bowl?
CODE
integer handle;
integer my_channel;
float t =25.0;

start_listen(){
llListenControl(handle, TRUE);
llSetTimerEvent(t);
}

stop_listen(){
llListenControl(handle,FALSE);
llSetTimerEvent(0.0);
}


default
{
state_entry(){
my_channel = (integer)("0x"+llGetSubString((string)llGetKey(),-8,-1));//calculate channel from my uuid
handle = llListen(my_channel, "", NULL_KEY,"");
llListenControl(handle, FALSE);
}

touch_start(integer n){
start_listen();
// llDialog stuff
}

listen(integer channel, string name, key id, string message){
stop_listen();
// give candy
}

timer(){
stop_listen();// in case the avatar ignores the llDialog menu
}
}
Rolig Loon
Not as dumb as I look
Join date: 22 Mar 2007
Posts: 2,482
10-21-2009 10:33
Hmm... Using llListenControl takes care of the orphaned listen handler problem, but what happens if two agents head for candy at once? Doesn't the first person's click on a dialog button turn off the listen handle so the other person doesn't get any? /me scratches head....
_____________________
It's hard to tell gender from names around here but if you care, Rolig = she. And I exist only in SL, so don't ask.... ;)

Look for my work in XStreetSL at
Innula Zenovka
Registered User
Join date: 20 Jun 2007
Posts: 1,825
10-21-2009 10:47
From: Rolig Loon
Hmm... Using llListenControl takes care of the orphaned listen handler problem, but what happens if two agents head for candy at once? Doesn't the first person's click on a dialog button turn off the listen handle so the other person doesn't get any? /me scratches head....
I'm not sure.. I guess the reply from the first person to choose their candy turns off the listener for the more dilatory one. But for something like a candy bowl, I'm not sure how much of a problem that would be in practice.. I'd worry about it for a vendor, but maybe I'm just being lazy.
Meade Paravane
Hedgehog
Join date: 21 Nov 2006
Posts: 4,845
10-21-2009 10:53
From: Innula Zenovka
I'm not sure.. I guess the reply from the first person to choose their candy turns off the listener for the more dilatory one. But for something like a candy bowl, I'm not sure how much of a problem that would be in practice.. I'd worry about it for a vendor, but maybe I'm just being lazy.

/me nods. For a free candy vendor, I'd pick a random channel at state_entry then just throw a dialog at anybody who touched and any listener hits would get the object they wanted.. No need to have a single line of people queueing up for candy!!
_____________________
Tired of shouting clubs and lucky chairs? Vote for llParcelSay!!!
- Go here: http://jira.secondlife.com/browse/SVC-1224
- If you see "if you were logged in.." on the left, click it and log in
- Click the "Vote for it" link on the left
Rolig Loon
Not as dumb as I look
Join date: 22 Mar 2007
Posts: 2,482
10-21-2009 11:00
Yup. There's not much point getting fancy for free candy. Let people click a second time. :p Still, it's kind of an interesting logic problem.
_____________________
It's hard to tell gender from names around here but if you care, Rolig = she. And I exist only in SL, so don't ask.... ;)

Look for my work in XStreetSL at
diamond Marchant
Registered User
Join date: 16 Jun 2009
Posts: 4
10-21-2009 14:17
Rolig is correct in that Innula's solution solves the orphaned listener problem but creates a race condition when two agents touch the candy bowl at the same time. Only one gets candy.

The candy bowl example, while trivial, was motivated by a real experience in SL. I received a group notice telling me that free Halloween stuff was available. Upon TPing in, I found myself in a mob scene around a candy bowl. Not sure how that candy bowl was scripted but one hopes it was delivering the goods :)
Pete Littlebird
Registered User
Join date: 30 Sep 2009
Posts: 44
10-21-2009 15:54
Would creating a list of channels, their listener and the time of its creation when touched for each of the detected touches, polling it for timeouts in the timer event, and clearing it when receiving a response make any sense?
Innula Zenovka
Registered User
Join date: 20 Jun 2007
Posts: 1,825
10-22-2009 06:48
What happens if you just have a suitably random channel, like one based on the prim's uuid, turn on the listener and start a timer whenever someone touches the bowl, and turn it off with the timer? That, as far as I can see, will restart the timer every time someone touches it, so if someone touches the prim while it's in use, then that just extends the time the listener remains activated.

Since you're saying llGiveInventory(id, candy), presumably everyone should get what they've chosen. OK, you're keeping the listener open for 15 or 20 seconds longer than you need, but that's not the end of the world.
Yumi Murakami
DoIt!AttachTheEarOfACat!
Join date: 27 Sep 2005
Posts: 6,860
10-22-2009 11:01
If an interaction starts while one is already in progress, re-use the existing channel. (This means not applying an id filter to the listen.) Count the number of interactions that are ongoing, and when it falls to zero, close the listen.

But.. honestly, the amount of lag created by an open listen on a negative channel isn't that high, and by this stage your channel management code is probably consuming more load than it saves. If you were listening on 0, then absolutely make sure not to for any longer than you need, but if you're listening on -10928382..
diamond Marchant
Registered User
Join date: 16 Jun 2009
Posts: 4
10-23-2009 08:45
Yumi and Innula make good points. Here is a smaller script that always gives candy, even to the same agent with multiple dialogs, never orphans a listener, and has no race conditions unless you count the race with the timeout.

From: someone

integer TIMEOUT = 60;
integer STOP_TIMER = 0;
integer gListenHandle;
integer cmdChannel;
integer gCount = 0; // counting give candy events

integer randomChannel() {
return (integer)llFrand(-200000) - 100;
}

showDialog(key id, string text, list buttons) {

llDialog(id, text, buttons, cmdChannel);
llListenControl(gListenHandle, TRUE);
llSetTimerEvent(TIMEOUT);
}

default {
on_rez(integer start_param)
{
llResetScript();
}
state_entry()
{
cmdChannel = randomChannel();
gListenHandle = llListen(cmdChannel,"",NULL_KEY,"OK";);
llListenControl(gListenHandle, FALSE);
}
touch_start(integer total_number)
{
integer i;
// handle multiple touches, sim may be laggy
for (i=0;i < total_number;i++) {
showDialog(llDetectedKey(i), "Candy?",[]);
}
}
listen(integer channel, string name, key id, string message)
{
gCount++;
if (message == "OK";) // not really necessary if filtering on "OK"
llWhisper(0, "I would give "+llKey2Name(id)+" some if I had any ("+(string) gCount+";)";);
}
timer() {
llListenControl(gListenHandle, FALSE);
llSetTimerEvent(STOP_TIMER);
}
}



I used the message filter ("OK";) in llListen just to try it out. I also tried filtering by object name but could not get that to work (listener never heard anything).