Welcome to the Second Life Forums Archive

These forums are CLOSED. Please visit the new forums HERE

llSensorRepeat returning NULL_KEY???

Merlin Alphabeta
Registered User
Join date: 12 Feb 2006
Posts: 83
02-22-2006 20:08
basically:
I use llSensorRepeat("",llGetOwner(),AGENT,96,2*PI,.5); to make a floating follower

occasionally I want to look for anyone in the area - I use llSensor("",NULL_KEY,AGENT,20,2*PI); for that.

This is fired off logic run in a timer event repeating every .3 seconds

When it fires I get things detected - but llDetectedName(0) and llDetectedPos(0) return NULL_KEY

This used to work in my script - I would occasionally get these errors but occasionally get good results. I cleaned it up - making sure I always call llSensorRemove() manually even before the llSensor calls, optimizing it so the timer and sensor can call less often and still be effective, that sort of thing. Since I did all that it NEVER fires right. The Repeat fires right (returning good data, etc). I use a flag CustomSensor to tell me when I'm calling it the other way - and it is only during these calls that I get the NULL_KEY problem.

I'm wondering if the sensor events are staying queued - but in that case llDetectedName should return my name shouldn't it?

I don't really understand this behaviour - I'm wondering if I've uncovered some sort of bug. As near as I can tell from reading the annoyances, bugs, llSensor, llSensorRepeat, and sensor entries in the wiki my timings are good - they shouldn't be so fast that I'm stacking up sensor events and getting erroroneous calls I think.

Does anyone have any comments? Any Lindens want to look into the problem? I'll gladly share my script if it'll help...
Padraig Stygian
The thin mick
Join date: 15 Aug 2004
Posts: 111
02-22-2006 20:43
Try replacing NULL_KEY with "". I seem to recall that using NULL_KEY in sensor events sometimes used to cause hiccups.

Other than that, there's nothing I can say without seeing the script.
_____________________
(You): Aww! My pants won't rez! Does this texture look okay on me?

Incidental Radio :: Because nothing is by design
Now featuring Torley-tastic technomusic!
Merlin Alphabeta
Registered User
Join date: 12 Feb 2006
Posts: 83
02-22-2006 21:27
So I call llSensor("", "", ....) ?

Nope that didn't do it...

I'd really appreciate it if someone would help me fix this - I can promise the source for my conversation code as a reward! It's a pretty easy to use keyword-oriented command syntax, kind of like the old ultima games used.

Here's my code - just make a ball prim and drop it in. There's supposed to be a linked prim with that ai conversation code in it but I hacked up the listen event to listen to the following:

follow - should follow you
kill - should reset to follow mode
auto - this is the mode I'm having problems with

activate these by saying /navi follow, /navi kill, or /navi auto

When you type in /navi auto she'll start telling you what she's doing. Every now and then she'll say "looking for someone" and then whether or not the person found is on her list of people she's previously greeted. If she gets "00000000-0000-0000-0000-000000000000" she simply says the id and name. This is caught in the else side of the if(CustomerSensor == 0) statement in the sensor() event and then passed to greet_someone() to do the comparison - greet_someone used to do the looping and whatnot internally but I changed it to this to see if it fixed the problem...

BTW, sorry for the indention - my text editor sets tabs to 4 spaces a piece while IE sets them to 8 spaces each...

list dances = [ "BackFlip","dance1", "dance2", "dance3", "dance4" ];
integer WHICH = 0;
integer TOTAL = 5;
integer PERMS = 0;

integer boolReturn = 0;
integer boolReturning = 0;
integer selfdance = 0;
string ownerName;
vector ownerPos;
string shopMessage;
integer naviport = 1337;
integer ownerSaid;
key saidID;

float particle_color_cycle = 0.0;
integer CustomSensor = 0;
integer HIGH_BEAMS = 0;
integer MODE_FOLLOW = 0;
integer MODE_AUTO = 1;
integer MODE_ATTRACT = 2;
integer MODE_DANCE = 3;
integer MODE_SHOPGREET = 4;
integer MODE_RAVE = 5;
integer MODE_TELL = 6;
float current_rotation = 0;
vector rotation_axis = <0, 0, 1>;
vector rotation_offset = <0, 0, .1>;

integer ticks = 0;
integer tickdelay = 0;
integer ticksToBeBig = 0;
integer ticksToSayHi = 0;
integer ticktimeout = 3;

integer current_mode = 0;
float burst_time = 0;



float floatdist = 0.1; // This is the distance we bob up and down.
float floatmod = 0.025;

float floaty;
integer flippertog;
float floatbase;

list avatarsGreeted = [];
string avatarGreeting = NULL_KEY;
vector avatarPos;
rotation avatarRot;

vector offset =<-1,0.6,1>;
vector attractobject;
vector HOVER_BEHIND = <-1,0.6,1>;
vector HOVER_INFRONT = <1, 0, 1>;
vector HOVER_ATWAIST = <1, 0, .4>;
vector HOVER_ATFEET = <1, 0, 0>;
integer listenCallback = 0;
integer Detected;



float abs(float v)
{
if(v < 0)
return -1 * v;
else
return v;
}


mySetParticles() {
vector START_SCALE = < 0.1, 0.9, 0.0 >;
vector END_SCALE = < 0.9, 0.1, 0.0 >;
float red = 0.0;
float green = 0.0;
float blue = 0.0;
if(particle_color_cycle < 0.333)
{
red = abs(0.333 - particle_color_cycle) * 3;
}
if(particle_color_cycle > 0.667)
{
red = (particle_color_cycle - 0.667) * 3;
}
if(particle_color_cycle < 0.667)
{
green = 1 - abs(particle_color_cycle - 0.333) * 3;
}
if(particle_color_cycle > 0.333)
{
blue = 1 - abs(particle_color_cycle - 0.667) * 3;
}

vector START_COLOR = < red, green, blue >;
llSetColor(START_COLOR, 0);
vector END_COLOR = START_COLOR * 0.8;
float START_ALPHA = 0.5 ;
float END_ALPHA = 0.1;
integer INTERP_COLOR = TRUE;
integer INTERP_SCALE = TRUE;
integer EMISSIVE = TRUE;
string TEXTURE = "Navi Texture";
float AGE = 2.00;
float RATE = 0.03;
integer COUNT = 10;
float LIFE = 0.0;
integer PATTERN = PSYS_SRC_PATTERN_ANGLE_CONE;
float RADIUS = 0.00;
float ANGLE_BEGIN = 0;
float ANGLE_END = 0.30;
vector OMEGA = < 0.00, 0.00, 2.00 >;
integer FOLLOW_SRC = FALSE;
integer FOLLOW_VELOCITY = TRUE;
integer WIND = TRUE;
integer BOUNCE = TRUE;
float SPEED_MIN = 0.70;
float SPEED_MAX = 1.20;
vector ACCEL = < 0.00, 0.00, -0.40 >;
integer TARGET_POS = FALSE;
key TARGET = llGetKey();

if(HIGH_BEAMS == 0) {
RATE = 0.05;
COUNT = 2;
FOLLOW_VELOCITY = FALSE;
WIND = FALSE;
SPEED_MIN = 0;
ACCEL = <0, 0, 0>;
PATTERN = PSYS_SRC_PATTERN_DROP;
} else {
}

list particle_parameters = [
PSYS_PART_FLAGS, (
( EMISSIVE * PSYS_PART_EMISSIVE_MASK ) |
( BOUNCE * PSYS_PART_BOUNCE_MASK ) |
( INTERP_COLOR * PSYS_PART_INTERP_COLOR_MASK ) |
( INTERP_SCALE * PSYS_PART_INTERP_SCALE_MASK ) |
( WIND * PSYS_PART_WIND_MASK ) |
( FOLLOW_SRC * PSYS_PART_FOLLOW_SRC_MASK ) |
( FOLLOW_VELOCITY * PSYS_PART_FOLLOW_VELOCITY_MASK ) |
( TARGET_POS * PSYS_PART_TARGET_POS_MASK ) ),
PSYS_PART_START_COLOR, START_COLOR,
PSYS_PART_END_COLOR, END_COLOR,
PSYS_PART_START_ALPHA, START_ALPHA,
PSYS_PART_END_ALPHA, END_ALPHA,
PSYS_PART_START_SCALE, START_SCALE,
PSYS_PART_END_SCALE, END_SCALE,
PSYS_SRC_PATTERN, PATTERN,
PSYS_SRC_BURST_PART_COUNT, COUNT,
PSYS_SRC_BURST_RATE, RATE,
PSYS_PART_MAX_AGE, AGE,
PSYS_SRC_ACCEL, ACCEL,
PSYS_SRC_BURST_RADIUS, RADIUS,
PSYS_SRC_BURST_SPEED_MIN, SPEED_MIN,
PSYS_SRC_BURST_SPEED_MAX, SPEED_MAX,
PSYS_SRC_TARGET_KEY, TARGET,
PSYS_SRC_ANGLE_BEGIN, ANGLE_BEGIN,
PSYS_SRC_ANGLE_END, ANGLE_END,
//PSYS_SRC_INNERANGLE, INNERANGLE,
//PSYS_SRC_OUTERANGLE, OUTERANGLE,
PSYS_SRC_OMEGA, OMEGA,
PSYS_SRC_MAX_AGE, LIFE,
PSYS_SRC_TEXTURE, TEXTURE
];
llParticleSystem( particle_parameters );
}

startup()
{
floatbase = offset.z;
flippertog = TRUE;
vector pos = llGetPos();
llSetScale(<0.02, 0.02, 0.02>;);
llSetStatus(STATUS_ROTATE_Z,TRUE);
key id = llGetOwner();
lookForOwner();
if(listenCallback > 0)
llListenRemove(listenCallback);
listenCallback = llListen(0, "", NULL_KEY, "";);
llMinEventDelay(.05);
llSetTimerEvent(.3); // reset the alarm clock
naviTitle();
}

floatcountplus()
{
offset = <offset.x,offset.y,floatbase>; //Return to base z offset.
if (flippertog == TRUE)//Going up?
{
floaty = floaty + floatmod;
if (floaty > floatdist) // We just added, are we capped?
flippertog = FALSE;
} else {
floaty = floaty - floatmod;
if (floaty < -floatdist) // We just added, are we capped?
flippertog = TRUE;
}
floatbase = offset.z;
offset = <offset.x,offset.y,offset.z+floaty>;
}

lookForOwner() {
llSensorRemove();
llSensorRepeat("",llGetOwner(),AGENT,200,2*PI,.5);
}

integer particles_on = 1;

integer greet_someone(string name, key id, vector pos, rotation myrot) {
if(id == NULL_KEY) {
llOwnerSay("id = " + (string)id + " name = " + name);
return -1;
}
integer index = llListFindList(avatarsGreeted, [name]);
if(index == -1) {
llOwnerSay("Greeting " + name);
avatarsGreeted += [name];
avatarGreeting = name;
Detected = 1;
avatarPos = pos;
avatarRot = myrot;
llMoveToTarget(pos+offset*avatarRot,.3);
llLookAt(pos, .1 , 1);
return 1;
}
llOwnerSay("Already greeted: " + name);
return -1;
}

naviTitle() {
llSetText("Navi", <.2, 1, .2>, 1.0);
}

default
{
state_entry()
{
startup();
}

listen(integer channel, string name, key id, string message)
{
string command = llGetSubString(message, 6, llStringLength(message));
integer first_space = 0;
string first_command = NULL_KEY;
string message_remainder = NULL_KEY;

//I know now there are more efficient ways to do this... this was my first parser
for(first_space = 0; first_space < llStringLength(command); first_space++)
{
if(llGetSubString(command, first_space, first_space) == " ";)
{
first_command = llGetSubString(command, 0, first_space - 1);
message_remainder = llGetSubString(command, first_space + 1, llStringLength(command));
}
}

if(first_command == NULL_KEY)
first_command = command;

if(llGetSubString(message, 0, 4) == "/navi";)
{
saidID = id;
if(id == llGetOwner()) {
ownerSaid = TRUE;
} else {
ownerSaid = FALSE;
}
saidID = (string)id;
if(first_command == "kill";)
{
particles_on = 0;
mySetParticles();
current_mode = MODE_FOLLOW;
offset = HOVER_BEHIND;
HIGH_BEAMS = 0;
lookForOwner();
}
if(first_command == "shopgreet" && ownerSaid == TRUE)
{
avatarsGreeted = [];
if(message_remainder != NULL_KEY) {
current_mode = MODE_SHOPGREET;

ownerPos = llGetPos();
shopMessage = message_remainder;
HIGH_BEAMS = 1;
llSensorRemove();
llSensorRepeat("","",AGENT,20,2*PI,2);
llSay(0, "Ok I'm going to tell people " + message_remainder);
} else {
llSay(0, "Tell me what you would like to say like {/navi shopgreet message}";);
}
}
llMessageLinked(LINK_ALL_OTHERS, naviport, first_command, (key)name);
if(first_command == "follow";)
{
current_mode = MODE_FOLLOW;
offset = HOVER_BEHIND;
HIGH_BEAMS = 0;
lookForOwner();
}
if(first_command == "auto";)
{
avatarsGreeted = [];
current_mode = MODE_AUTO;
offset = HOVER_BEHIND;
HIGH_BEAMS = 0;
lookForOwner();
}
}
}

link_message(integer sender_num, integer num, string str, key id) {
//this is how we process commands from the ai script - since its not present, this code should never run.
string first_command = (string)id;
if(first_command == "";) {
llSay(0, str);
return;
}
if(first_command == "glow";)
{
if(particles_on == 1) {
particles_on = 0;
llParticleSystem( [] );
}
else
{
particles_on = 1;
mySetParticles(); // touch to reset/turn on the particles
}
}
if(ownerSaid == TRUE && first_command == "follow";)
{
current_mode = MODE_FOLLOW;
offset = HOVER_BEHIND;
HIGH_BEAMS = 0;
lookForOwner();
}
if(ownerSaid == TRUE && first_command == "auto";)
{
avatarsGreeted = [];
current_mode = MODE_AUTO;
offset = HOVER_BEHIND;
HIGH_BEAMS = 0;
lookForOwner();
}
if(ownerSaid == TRUE && first_command == "attract";)
{
avatarsGreeted = [];
current_mode = MODE_ATTRACT;
offset = HOVER_INFRONT;
HIGH_BEAMS = 1;
lookForOwner();
}
if((ownerSaid == TRUE && first_command == "dance";) || first_command == "danceplease";)
{
if(ownerSaid == TRUE)
selfdance = 1;
llRequestPermissions(saidID, PERMISSION_TRIGGER_ANIMATION);
current_mode = MODE_DANCE;
HIGH_BEAMS = 1;
llSensorRemove();
llSensorRepeat("",saidID,AGENT,5,2*PI,.5);
}
if(first_command == "rave";)
{
current_mode = MODE_RAVE;
HIGH_BEAMS = 1;
}
if(first_command == "tell";)
{
current_mode = MODE_TELL;
HIGH_BEAMS = 0;
}
if(first_command == "big";) {
llSetScale(<.1, .1, .1>;);
//stay big for five seconds
ticksToBeBig = 15;
}
if(str != NULL_KEY)
llSay(0, str);
}


on_rez(integer start_param)
{
startup();
}

no_sensor() {
llOwnerSay("Noone here!";);
if(current_mode == MODE_AUTO || current_mode == MODE_ATTRACT) {
lookForOwner();
}
}

sensor(integer total_number)
{
Detected = 0;
if(CustomSensor == 0)
{
if(current_mode == MODE_FOLLOW) {
ownerName = llDetectedName(0);
floatcountplus();
vector pos = llDetectedPos(0);
ownerPos = pos;
llMoveToTarget(pos+offset*llDetectedRot(0), .3);
llLookAt(pos, .1 , 1);
}
if(current_mode == MODE_DANCE || current_mode == MODE_ATTRACT || current_mode == MODE_AUTO) {
floatcountplus();
if(((current_mode == MODE_AUTO || current_mode == MODE_ATTRACT) && boolReturn == 1) ||
(current_mode == MODE_DANCE))
{
vector pos = llDetectedPos(0);
ownerPos = pos;
llOwnerSay("Moving auto/dance/attract";);
llMoveToTarget(pos+offset*llDetectedRot(0), .3);
llLookAt(pos, .1 , 1);
}
}
if(current_mode == MODE_SHOPGREET) {
Detected = 0;
integer i;
for(i = 0; i < total_number && Detected == 0; i++) {
if(greet_someone(llDetectedName(i), llDetectedKey(i), llDetectedPos(i), llDetectedRot(i)) == 1) {
ticks = 4;
return;
}
}
}
}else { // CustomSensor != 0
CustomSensor = 0;
if(current_mode == MODE_ATTRACT | current_mode == MODE_AUTO) {
Detected = 0;
integer i;
llOwnerSay("Looking for someone!";);
for(i = 0; i < total_number && Detected == 0; i++) {
if(greet_someone(llDetectedName(i), llDetectedKey(i), llDetectedPos(i), llDetectedRot(i)) == 1) {
llOwnerSay("Found someone";);
boolReturn = 0;
ticksToSayHi = 3;
ticks = 10;
if(current_mode == MODE_AUTO) {
particle_color_cycle = 0.5;
mySetParticles();
}
return;
}
}
//this code is run when the above loop falls through without finding someone
//boolReturn tells the system that its currently looking for its owner
//and ticks is how many timer() events to go by until the next piece of logic executes
boolReturn = 1;
ticks = 10;
if(current_mode == MODE_AUTO) {
particle_color_cycle = 0.333;
mySetParticles();
}
lookForOwner();
}
}
}

timer() {
if(ticksToSayHi > 0) {
ticksToSayHi --;
if(ticksToSayHi == 0) {
llSay(0, "Hey Listen " + avatarGreeting);
llSetText("Hey!", <1, .7, .2>, 1.0 );
}
}
if(ticksToBeBig > 0) {
ticksToBeBig--;
if(ticksToBeBig == 0) {
llSetStatus(STATUS_PHYSICS, FALSE);
llSetScale(<0.02, 0.02, 0.02>;);
llSetStatus(STATUS_PHYSICS, TRUE);
}
}

if(particles_on == 0)
{
llParticleSystem( [ ] );
llSetColor(<.8, .8, .8>, 0);
}

if(current_mode == MODE_FOLLOW)
{
particle_color_cycle += 0.05;
if(particle_color_cycle > 1.0)
particle_color_cycle -= 1.0;
mySetParticles();
naviTitle();
return;
}

if(current_mode == MODE_AUTO)
{ //this is the mode I'm having problems with
mySetParticles();
if(ticks == 0) { // timer is up!
if(boolReturn == 0) { //we WEREN'T hovering around our owner so let's go do that on the next sensor sweep
particle_color_cycle = 0.333;
mySetParticles();
boolReturn = 1;
lookForOwner();
ticks = 10;
} else {
// boolReturn != 0, so it's time to go look for someone to greet
particle_color_cycle = 0.677;
mySetParticles();
CustomSensor = 1;
llSensorRemove();
llSensor("", "", AGENT, 20, 2*PI);
ticks = 10;
}
} else {
//decrement ticks
ticks--;
}
}

if(current_mode == MODE_ATTRACT) //this is how mode_auto used to look when it worked better
{
particle_color_cycle += 0.05;
if(particle_color_cycle > 1.0)
particle_color_cycle -= 1.0;
mySetParticles();
if(avatarGreeting == NULL_KEY) {
CustomSensor = 1;
llSensorRemove();
llSensor("", "", AGENT, 40, 2*PI);
} else {
llMoveToTarget(avatarPos+offset*avatarRot,.3);
ticktimeout = 4;
ticks += 1;
if(ticks > 1) {
boolReturn = 1;
lookForOwner();
}
if(ticks > ticktimeout) {
avatarGreeting = NULL_KEY;
boolReturn = 0;
boolReturning = 0;
ticks = 0;
}
}
return;
}

if(current_mode == MODE_DANCE)
{
naviTitle();
particle_color_cycle += 0.09;
if(particle_color_cycle > 1.0)
particle_color_cycle -= 1.0;
mySetParticles();
vector myoffset = HOVER_INFRONT;
myoffset = myoffset*llAxisAngle2Rot(rotation_axis, current_rotation);
current_rotation = current_rotation + .5;
if(current_rotation > 2*PI) {
current_rotation = current_rotation - 2*PI;
}
llMoveToTarget(attractobject+myoffset,.05);
llLookAt(attractobject, .1 , 1);
ticks += 1;
ticktimeout = 4;
if(ticks > ticktimeout) {
ticks = 0;
if(selfdance == 1) {
PERMS = llGetPermissions();
if (PERMS & PERMISSION_TRIGGER_ANIMATION)
{
llStopAnimation(llList2String(dances, WHICH - 1));
WHICH = (integer)llFrand(TOTAL);
llStartAnimation(llList2String(dances, WHICH));
}
}
return;
}
}

if(current_mode == MODE_SHOPGREET)
{
particle_color_cycle = .333;
mySetParticles();
if(ticks > 0) {
ticks -= 1;
} else {
naviTitle();
llSay(0, "Hi! I'm a {Navi} {fairy}! " + ownerName + " has asked me to {greet} anyone coming by!";);
llSay(0, "You can talk to me by saying {/navi word} Try {/navi wordlist} to get you started";);
llSay(0, shopMessage);
llMoveToTarget(ownerPos, 1);
}
return;
}

if(current_mode == MODE_RAVE)
{
particle_color_cycle = .333;
mySetParticles();
return;
}

if(current_mode == MODE_TELL)
{
particle_color_cycle = .5;
mySetParticles();
return;
}
}

touch(integer i) {
llSay(0, "Hi! I'm a {Navi}! If you want to talk to me say {/navi word}. You might want to try {/navi wordlist} to get you started";);
}
}
Lindsey Dassin
Fallen Angel
Join date: 14 Sep 2005
Posts: 33
02-22-2006 23:34
OMG, you're the same Merlin i met in Goguen, right? I had the moody fire faeries...? How are you? :)

That's quite a chunk of code -- wow! I think i know why you're getting the NULL_KEY names and keys in your sensor event, though.

Mmmkay, in your code, you do a lot of llSensor()s and a lot of llSensorRepeats(), and some of these are controlled by timer events. LSL events run asynchronously, and as far as i can tell, they can run concurrently or even run parallel. So you could have a listen event running at the same time as a sensor, for example.

So what happens if you have a chain of events that looks like this?


"A" starts a sensor sweep with llSensorRepeat() or llSensor()
"B": The sensor sweep finishes, and the sensor() event is triggered
"A" Removes the sensor with llSensorRemove() while the sensor event "B" is still running
"B": After the sensor is removed by "A", we call llDetectedKey(), llDetectedName, etc.


Here's an example chunk of code to do just that:

CODE

default {
state_entry() {
llSensorRepeat( "", NULL_KEY, AGENT, 20.0, PI, 2.0 );

// Wait for the sensor event to trigger
llSleep( 3.0 );

// Then remove the sensor
llSensorRemove();
}

sensor( integer total_num ) {
integer i;

// Wait for state_entry to remove the sensor before doing our report
llSleep( 2.0 );

for( i = 0; i < total_num; ++i ) {
llSay( 0, llDetectedName( i )
+ " at "
+ (string)llDetectedPos( i ));
}
}

no_sensor() {
llOwnerSay( "I didn't find anybody!" );
}
}


If you drop that code into a box and wait a few seconds, you'll get the same NULL_KEY for a name, at position <0, 0, 0>. Apparently when llSensorRemove() is called, all of the retrieved sensor information is wiped out -- even if you're in the middle of a sensor event somewhere else!

Fixing this will add another layer of complexity to your already complicated code. Have you maybe considered using a few simpler states than one really big one you have now?
_____________________
:wq
Merlin Alphabeta
Registered User
Join date: 12 Feb 2006
Posts: 83
02-23-2006 05:41
Yeah I was thinking of using multiple states - sounds like that may be the way to go here - I kind of hacked in my own state logic as that current_mode - thanks for the advice!
Lindsey Dassin
Fallen Angel
Join date: 14 Sep 2005
Posts: 33
02-23-2006 09:51
I'm always glad to help. :)
_____________________
:wq
Lex Neva
wears dorky glasses
Join date: 27 Nov 2004
Posts: 1,361
02-23-2006 10:34
From: Lindsey Dassin
Mmmkay, in your code, you do a lot of llSensor()s and a lot of llSensorRepeats(), and some of these are controlled by timer events. LSL events run asynchronously, and as far as i can tell, they can run concurrently or even run parallel. So you could have a listen event running at the same time as a sensor, for example.


I just woke up and I'm too tired to sift through that code up there, but I caught on this and wanted to clear up a misconception. LSL events run synchronously. You won't have a listen event running at the same time as a sensor. However, you can have a sensor event queued up and waiting to run while you're in a listen event handler. Still, I don't think sensors are supposed to EVER return NULL_KEY... that makes no sense. Maybe bug report the script, but it'd probably help them if you could pare it down to the minimal script that still exhibits the problem.
Lindsey Dassin
Fallen Angel
Join date: 14 Sep 2005
Posts: 33
02-23-2006 11:49
From: Lex Neva
I just woke up and I'm too tired to sift through that code up there, but I caught on this and wanted to clear up a misconception. LSL events run synchronously. You won't have a listen event running at the same time as a sensor. However, you can have a sensor event queued up and waiting to run while you're in a listen event handler. Still, I don't think sensors are supposed to EVER return NULL_KEY... that makes no sense. Maybe bug report the script, but it'd probably help them if you could pare it down to the minimal script that still exhibits the problem.


The script i posted does exhibit the behavior of "sensor returns null", and was intended to be fairly minimal. If you remove the llSleep() from state_entry, the sensor event never happens. If you remove the llSleep() from the sensor event instead, the script will report avatars in the immediate area. With both llSleep() statements present, the script will report a NULL_KEY for every avatar in the area.

I wouldn't doubt that LSL events really are synchronous and that i'm stuffing my foot into my mouth again. Simultaneously, i am at a loss to explain this behavior if events are synchronous, though it makes perfect sense if they are asynchronous. If events are synchronous, maybe the llSleep() allows the next queued event to begin/resume execution...? I realize the wiki says that shouldn't happen, but what i have presented above is evidence of what i've seen.

In light of your post, i plan to experiment with this some more, tonight. Unless anyone objects, i'll post my results here.
_____________________
:wq
Ziggy Puff
Registered User
Join date: 15 Jul 2005
Posts: 1,143
02-23-2006 11:59
Well, llSleep is supposed to stop that script in place for the specified time, and not allow other events to run. A good way to debug this would be to add llSays before and after each llSleep, and print out where you are in the script. I would be very surprised if you didn't find that the state_entry ran to completion, and the sensor handler ony executed after the state_entry handler had finished.

I might be wrong here, but doesn't llSensorRepeat fire the first sensor after the specified timeout? So in state_entry, you're setting up the sensor to go off after 2 seconds, then you sleep for 3 seconds. In that time, the script is frozen, but in the background, after 2 seconds the sensor does fire, and the results get stored in the event queue. 1 second later, the script wakes up and deletes the sensor.

My guess is that this delete doesn't properly clean up the event that was in the queue. So after state_entry is done, the sensor event does fire, but it's got partial information available to it, because some of the information got cleared out when you deleted the sensor. So you get the NULL_KEY scenario. At least, that's my theory. I'm not sure why having a sleep inside the sensor event makes a difference... maybe the background processing is smart enough to wait until the sensor event fires before cleaning out the saved sensor data, but not smart enough to wait until the sensor event *finishes*. That's why without the delay you can still read the sensor data, but put a delay in the sensor event, and now the sensor data is gone.

Either way, please post the results of any further experiments you run, I for one am definitely interested :)
Merlin Alphabeta
Registered User
Join date: 12 Feb 2006
Posts: 83
02-23-2006 13:11
Well I rewrote completely to remove llSensorRepeat! I just call llSensor - and it's always the last thing before I return - so we'll see how that works tonight when I get off work. Assuming it runs (I'm pretty confident) I've got some "AI" goodness for you Lindsey (ever notice when game programmers and other programmers use the term AI they're talking about completely different things???)

I think that submitting the bug report is a good idea - using Lindsey's code, if you don't mind?
Zonax Delorean
Registered User
Join date: 5 Jun 2004
Posts: 767
02-23-2006 13:33
I am also suspicious of llSensorRepeat... Sometimes a rare and unexplainable bug comes up, it seems if the sensor just 'stops sensing' (some of) the things it sensed in the past.

I can't really tell much more than that, and it might be that my code has the problem (but the same code has worked okay till a few months ago). My sensor is set to 'sense all', though, so it might be just overloading when too much objects are around.
_____________________
Lindsey Dassin
Fallen Angel
Join date: 14 Sep 2005
Posts: 33
02-23-2006 13:42
From: Ziggy Puff
I might be wrong here, but doesn't llSensorRepeat fire the first sensor after the specified timeout? So in state_entry, you're setting up the sensor to go off after 2 seconds, then you sleep for 3 seconds. In that time, the script is frozen, but in the background, after 2 seconds the sensor does fire, and the results get stored in the event queue. 1 second later, the script wakes up and deletes the sensor.

My guess is that this delete doesn't properly clean up the event that was in the queue. So after state_entry is done, the sensor event does fire, but it's got partial information available to it, because some of the information got cleared out when you deleted the sensor. So you get the NULL_KEY scenario. At least, that's my theory. I'm not sure why having a sleep inside the sensor event makes a difference... maybe the background processing is smart enough to wait until the sensor event fires before cleaning out the saved sensor data, but not smart enough to wait until the sensor event *finishes*. That's why without the delay you can still read the sensor data, but put a delay in the sensor event, and now the sensor data is gone.


That makes sense, but the issue of llSleep() in the sensor event still just doesn't feel right to me. *sigh* ...and i've hand my mind on it so much today that i'm starting to seriously doubt if i'm remembering last night's events clearly or not! I'll do some more experiments tonight and post the results.

From: Ziggy Puff
Either way, please post the results of any further experiments you run, I for one am definitely interested :)


Okie, will do. Either way, we'll all have something to smile about when all is said and done! :)
_____________________
:wq
Lindsey Dassin
Fallen Angel
Join date: 14 Sep 2005
Posts: 33
02-23-2006 14:11
From: Merlin Alphabeta
Well I rewrote completely to remove llSensorRepeat! I just call llSensor - and it's always the last thing before I return - so we'll see how that works tonight when I get off work. Assuming it runs (I'm pretty confident) I've got some "AI" goodness for you Lindsey (ever notice when game programmers and other programmers use the term AI they're talking about completely different things???)

I think that submitting the bug report is a good idea - using Lindsey's code, if you don't mind?


Merlin, when i first developed this script, i used llSensor() instead of llSensorRepeat(), and later converted it over to llSensorRepeat() so it would more closely resemble your code. I got the same results with llSensor(): llSensorRemove() will remove all accumulated sensor data, regardless of how it got there...

Sure, it's absolutely okay if you use my code for the bug report, but would it be okay if i asked you to wait until after tonight to submit it? I'm still not entirely convinced that this even is a bug. I know, it's just my intuition giving me the wrong ideas, but could you indugle me anyway? ^.^;
_____________________
:wq
Lindsey Dassin
Fallen Angel
Join date: 14 Sep 2005
Posts: 33
Survey Says...?
02-23-2006 18:21
To test synchronous vs. asynchronous events, i simply reported what states are doing by calling llOwnerSay() at the beginning and end of each event. Please note the lack of llSleep() in the sensor event. Here's the code:

CODE

default {
state_entry() {
llOwnerSay( "Entering state_entry()" );

llSensorRepeat( "", NULL_KEY, AGENT, 20.0, PI, 2.0 );
llSleep( 3.0 );
llSensorRemove();

llOwnerSay( "Leaving state_entry()" );
}

sensor( integer total_num ) {
llOwnerSay( "Entering sensor()" );

integer i;
for( i = 0; i < total_num; ++i ) {
llSay( 0, llDetectedName( i ) + " at "
+ (string)llDetectedPos( i ));
}

llOwnerSay( "Leaving sensor()" );
}

no_sensor() {
llOwnerSay( "I didn't find anybody!" );
}
}


I took the llSleep() out of the sensor event, so it should report my name, as i claimed earlier. For asynchronous events, we should see this:

CODE

Entering state_entry()
Entering sensor()
"Lindsey Dassin at (vector)"
Leaving sensor()
Leaving state_entry()


And for synchronous we should see this:

CODE

Entering state_entry()
Leaving state_entry()
Entering sensor()
"Lindsey Dassin at (vector)"
Leaving sensor()


Here are the results:

CODE

Object: Entering state_entry()
Object: Leaving state_entry()
Object: Entering sensor()
Object: 00000000-0000-0000-0000-000000000000 at <0.00000, 0.00000, 0.00000>
Object: Leaving sensor()


That's not my name in the output. It's a NULL_KEY.


Conclusion:

1: LSL events are indeed synchronous.

2: The NULL_KEY values supplied to the sensor() event are the result of a bug.

3: Lindsey is seeing things. Or ditzy. Or both. In any case, she should not post when she's seeing things. Or feeling ditzy. Or both. ^_^;; I'm sorry for making such a big deal out of nothing.

Edited for grammatical errors. I hate those!
_____________________
:wq
Merlin Alphabeta
Registered User
Join date: 12 Feb 2006
Posts: 83
02-24-2006 05:36
From: Lindsey Dassin
Merlin, when i first developed this script, i used llSensor() instead of llSensorRepeat(), and later converted it over to llSensorRepeat() so it would more closely resemble your code. I got the same results with llSensor(): llSensorRemove() will remove all accumulated sensor data, regardless of how it got there...


Well I changed it to calling llSensor instead of llSensorRepeat so I could be sure I was only calling a sensor when it was required - works great, BTW!!!! All I gotta do now is figure out how to make her wings flap and I'm done!

From: Lindsey Dassin

Sure, it's absolutely okay if you use my code for the bug report, but would it be okay if i asked you to wait until after tonight to submit it? I'm still not entirely convinced that this even is a bug. I know, it's just my intuition giving me the wrong ideas, but could you indugle me anyway? ^.^;


Sure thing! Just let me know!
Lex Neva
wears dorky glasses
Join date: 27 Nov 2004
Posts: 1,361
02-24-2006 09:26
From: Lindsey Dassin

Conclusion:

1: LSL events are indeed synchronous.

2: The NULL_KEY values supplied to the sensor() event are the result of a bug.

3: Lindsey is seeing things. Or ditzy. Or both. In any case, she should not post when she's seeing things. Or feeling ditzy. Or both. ^_^;; I'm sorry for making such a big deal out of nothing.


Excellent work! It seems like Ziggy was exactly right; if you kill a SensorRepeat while the results of it are sitting in the event queue, it seems to wipe the detected info for that event, but still let the event run. That's most definitely a bug... an obscure one, but a bug.

I suppose it could be argued that, since you removed the repeating sensor, you shouldn't be getting any results from it immediately and from that point forward as soon as you call llSensorRemove(), and that's why they wiped it... but I dunno. I think it might make sense to remove the sensor event entirely from the queue or something, but honestly, I'd actually prefer to see the sensor that did fire stay in the event queue even after calling llSensorRemove(). My interpretation would be that calling llSensorRemove() should cease to trigger new sensors every n seconds, but the sensor event that's already in the queue happened before the llSensorRemove() call. Anyway, that babble all assumes that this was on purpose, and it's quite possible that it wasn't; that it's simply a bug.

That said, this is a nice simple reproducible test case, and I think you should bug report this script. LL QA folks don't want to see that whole fairy script, they want to see something sweet and to the point like this :)
Ziggy Puff
Registered User
Join date: 15 Jul 2005
Posts: 1,143
02-24-2006 09:36
From: someone
I'd actually prefer to see the sensor that did fire stay in the event queue even after calling llSensorRemove()


Me too. Going into an event queue and removing events that have already been posted seems wrong. And I think it works the same way with timers - calling llSetTimerEvent(0) stops further timer events, but doesn't touch events that are already in the queue. I found that out the hard way :)

Can't get in-game now, but someone could try out a similar test with timers:

CODE

integer i = 0;

default
{
state_entry()
{
llSetTimerEvent(1.0);
llSleep(5.0);
llSetTimerEvent(0.0);
}

timer()
{
llOwnerSay((string)i++);
}
}


Or something along those lines.

Thanks for taking the time to test this. I think you should bug report it too, and your final script is nice and small and easy to follow.
Lindsey Dassin
Fallen Angel
Join date: 14 Sep 2005
Posts: 33
02-24-2006 19:39
Lex, i apologize for being skeptical about your initial response. It's just that i could not in good conscience call myself a scientist if i wasn't willing to test propositions that i didn't believe to be true. Thanks for being patient with me, and equally, thanks for pointing me in the right direction! :)

I submitted a bug report. Here's the code i submitted, which should be as minimal as Ziggy's earlier posting:

CODE

default {
state_entry() {
llSensorRepeat( "", NULL_KEY, AGENT, 20.0, PI, 2.0 );
llSleep( 3.0 );
llSensorRemove();
}

sensor( integer total_num ) {
llOwnerSay( llDetectedName( 0 ) + " at " + (string)llDetectedPos( 0 ));
}
}


Merlin: I look forward to seeing Navi! ^_^

Still, that would be wonderful if events were asynchronous: One table, two forks, two knives, and four philosophers -- i'm so there!
_____________________
:wq
Lex Neva
wears dorky glasses
Join date: 27 Nov 2004
Posts: 1,361
02-25-2006 09:55
From: Lindsey Dassin
Lex, i apologize for being skeptical about your initial response. It's just that i could not in good conscience call myself a scientist if i wasn't willing to test propositions that i didn't believe to be true. Thanks for being patient with me, and equally, thanks for pointing me in the right direction! :)


Of course, if you're curious about something, don't take my word for it... the best way to learn is to see something firsthand :)