Jump to content

A systematic approach to debugging with llOwnerSay()


Profaitchikenz Haiku
 Share

You are about to reply to a thread that has been inactive for 921 days.

Please take a moment to consider if this thread is worth bumping.

Recommended Posts

Something we are saying to novice scripters over and over again is to "put some llOwnerSay() messages in your code to try and see what is actually happening".

Scattering llOwnerSay() messages through the code helps solve some problems but then introduces others:

You have to clean up the statements afterwards
There might also be some genuine llOwnerSay() statements that need to remain.
You might clean up the debug statements too soon and wish you'd hung unto them for just a bit more testing

I am starting to think that we are adding to their learning load here by forcing them to think out how to actually do this.  I am thinking of suggesting a systematic method we could refer them to that will give actual examples and help

The approach I have come to use works like this:

At the very top of the script, define a global

integer debug = 1;    // could be 0 if you also have a listen statement to toggle

in the area where functions are being declared, declare

Dout( string tellMeAbouIt)
{
    if(debug) llOwnerSay(tellMeAboutIt);
}

And then through the code, wherever a diagnostic is needed, add single statements of the form

Dout("In function (name) At point " + (string) llGetPos() + " " + (string) llGetRot() );

We need to stress here that anything can be cast to a string in LSL, (except the name of the function/state/event the code is currently in)

These messages are easily identifiable so cleaning up is quick and easy

The debug can also be turned on and off if there is a listen event using

if( msg == "debug") debug = ~debug;

This allows some diagnostic capability to be retained into early production code by initialising debug to 0 instead of 1 and then turning it on if required

I don't know if this post belongs in the LSL Library sub-forum, or if it is worth some of us working it over and making it a sticky.

Thoughts welcomed.

Practical example:

// debugging a listen event

listen(integer ch, string name, key id, string msg)
{
	Dout("Listen on channel " + (string) ch + " heard name " + name + " (" + (string) id + ") say >" + msg + "<");
	// and now do stuff
}

 

Edited by Profaitchikenz Haiku
  • Like 2
  • Thanks 1
Link to comment
Share on other sites

Thank you, Prof.  Many (most?) experienced LSL scripters probably use a system like this already, but it's not necessarily obvious to a newcomer.  As you say, peppering a script with llOwnerSay messages is easy, but can leave you with a lot of cleanup to deal with later.  I would guess that cleanup is less of an issue for real beginners than it is for advanced scripters, simply because beginners aren't writing long, complicated scripts.  Still, sooner or later we all get to a point where we look for something more elegant than llOwnerSay.  We either discover or reinvent a function like the one you are suggesting.

Now that you raise the topic, I wonder if it might be helpful to offer advice about where to put llOwnerSay ( or Dout() ) statements?  For example:

1. If you are using lists, what's in them at the moment?  You can check with llOwnerSay(llList2CSV(my_list) )  or something like  llOwnerSay(llList2String(my_list,i) ).

2. If you are using global variables, what's in them when the script enters an event where they will be used?  Put a llOwnerSay( "My_Variable = " + (string)My_variable) statement at the start of the event.

3., 4., 5. .....    Or maybe all of this is way too obvious?

  • Like 1
Link to comment
Share on other sites

6 minutes ago, Rolig Loon said:

Or maybe all of this is way too obvious?

It's only obvious when you already know it :)

And yes, I think we could get some mileage out of showing a script with several events in them and diagnostics to illustrate where to put debug messages and what sort of things to output.

So a worked example might be the best approach?

Link to comment
Share on other sites

When I add llOwnerSay for debugging I put the statement flushed left and unindented, which helps it stand out from the main code. Except for all those pesky wrapped lines of code.

When I do use a debug llOwnerSay wrapper function, I usually make its parameter a list, which cuts down on the explicit typecasting needed.

Debug (list message)
{
    llOwnerSay (llList2CSV (message));
}

Debug (["counter", counter, "value", value]);

Though sticking lists in there is less tidy.

  • Like 5
Link to comment
Share on other sites

I use a bitwise "verbose" integer check and throughout the script I have specific verbose values assigned. That is how I control what I want to see.

Debug(integer i, string s)
{
    if (verbose & i)
    {
        llOwnerSay(s);
    }
}
integer verbose;
default
{
    touch_end(integer i)
    {
        if (llDetectedKey(0) == llGetOwner())
        {
            verbose = (integer)llGetObjectDesc();
        }
    }
    link_message(integer i, integer ii, string message, key k)
    {
        Debug(0x1,"link_message() - " + message);
    }
    dataserver(key k, string data)
    {
        Debug(0x2,"dataserver() - " + data);
    }
    timer()
    {
        Debug(0x4,"timer() - " + llGetTimestamp());
    }
}

Depending on event usage, "verbose" could be assigned or checked one of many ways.

Edited by Lucia Nightfire
  • Like 2
Link to comment
Share on other sites

Rather than using llOwnerSay at all, may I recommend my practice (can't remember who introduced me to it, sorry) of doing this:

debugSay(string what){
	llRegionSay(iDebugChannel,what);
}

then I have an object or attachment listening on whatever iDebugChannel is for that particular script/project.    For iDebugChannel, I generate lists of long negative numbers now and again, save them in a text file, and simply cut and paste when necessary.

Makes both development and maintenance so much easier, and you don't need to worry about removing it.

Edited by Innula Zenovka
  • Like 1
Link to comment
Share on other sites

i like Prof's thought to come up with a standard recommendation for new people. I like the idea of it being stickied as well

i like Lucia's idea of passing a code to identify the event. There are 39 events so might have to go with whole number codes (1 thru 39) for the events in alphabetical order as the recommendation. Recommendation could be that custom codes start at 0x100

i agree with KT that the recommendation should be for DEBUG statement be flushed left on the line

l think the message parameter should be a string.  Examples can show how llDumpList2String can be used to pass multiple parameters

i like Innula's mention of using DEBUG_CHANNEL so that the message goes to the Script Warning/Error window. Am not sure it should be a formal recommendation tho as DEBUG_CHANNEL is an open channel. Two or more new people working independently in a sandbox can result in seeing messages from sources not themselves. (hint! hint! Rider Linden. llOwnerSayDebug please!)
 
i think DEBUG should be all caps, so the statement can be seen more easily

i think the flag should be named DEBUG_ECHO as this is what the DEBUG command does. Echoes the message to the scripter's console (viewer)

i think the echo should be preceded by the identifier "DEBUG"". So is clear to the new person that what they are seeing in their console is a DEBUG message   

putting all the thoughts so far together then something like

 

integer DEBUG_ECHO = TRUE;
DEBUG (integer code, string message)
{
    if (DEBUG_ECHO)
    {
        llOwnerSay("DEBUG " + (string)code + " " + message);
    }
}

default
{    
    touch_start(integer num_detected)
    {
        
DEBUG(38, llDetectedName(0) + " touched me");

    }
    
    listen(integer channel,string name, key id, string text)
    {

DEBUG(20, llDumpList2String([channel, name, id, text], " "));  

    }
}

 

Edited by Mollymews
typss
  • Like 1
Link to comment
Share on other sites

Good idea. I have something like that. I use Firestorm macros a lot for such things.

I have a more complex system I use for my NPCs. Error messages at various error levels are sent as link messages to a logging script. It maintains a large circular buffer of messages. When there's a serious problem, the error and the preceding 50 or so events are packaged up as an email and sent to me.  There's also a listener on DEBUG_CHANNEL, so that if there's a stack/heap collision, that's caught, and the events leading up to it are logged. Plus there are stall timers, to restart everything if something goes wrong. If they trip, the log of recent events gets sent in the resulting email.

Link to comment
Share on other sites

Good to see other peoples methods. I've used several such ones in the past, but the thing I was aiming at was the simplest possible system for newcomers so they can start debugging at once without having to look up several LSL wiki pages first.

Of the ideas so far, Molly's is perhaps the simplest since it only requires a newcomer to understand two functions, one of which they might already know and it's dead simple (llOwnerSay()), and one which is a bit more complicated but equally, simple to understand. Possibly below it you could show them the equivalent method of casting the individual items to a string, which is itself a vital debugging technique.

Innula's method is both technically superior and 'proper' in terms of the environment we live in, but I think it's forcing too many unknowns on a fresh starter, likewise Lucia's techniques (which I have actually used quite a bit) are also expecting a greater degree of knowledge than could be reasonably expected. Once they've got the basics, subsequent additions to that topic could explain to them the Debug channel, controlling behaviour from the object description, and how bitwise fields work.

What I think we need for a post to refer newcomers to is an example script that shows how to debug several of the typical events plus a user-defined function. It wants to be as simple and straightforward as possible, but then we could probably add to the thread with further examples showing the more complex methods.

 

Edited by Profaitchikenz Haiku
Link to comment
Share on other sites

Use the LSL Preprocessor in Firestorm.

 

In the top of your script, put this.

#define DEBUG

#ifdef DEBUG
Debug(string msg)
{
	llOwnerSay("(DEBUG): " + msg);
}
#endif

 

Then in areas where you need some debug output.


state_entry
{
  string foo = "bar";
  
  #ifdef DEBUG
  Debug("Something happened here: " + foo);
  #endif
}

 

When you don't need the debug code active, just remove or comment out the "#define DEBUG" and all of that code will be deactivated on the next recompile. Obviously you can edit the above code however you like, such as by including different defines for different levels of debug chatter. It's a bit more work than just using a boolean to turn on and off the debug chatter, but it saves on memory usage as none of the debug code is compiled when its deactivated.

Link to comment
Share on other sites

33 minutes ago, Ariu Arai said:

In the top of your script, put this.

#define DEBUG

#ifdef DEBUG
Debug(string msg)
{
	llOwnerSay("(DEBUG): " + msg);
}
#endif

Alternatively, to get rid of the user-function call (and to get exact line numbers):

#define DEBUG

#ifdef DEBUG
# define debug(message) llOwnerSay((string)__LINE__+": "+message)
#else
# define debug(nothing)
#endif

 

I don't think most people who are new to SL will also look into learning scripts (unless they have previous experience/interest). If they do, Firestorm's preprocessor brings many programming features from other languages into LSL.

  • Like 1
Link to comment
Share on other sites

49 minutes ago, Ariu Arai said:

When you don't need the debug code active, just remove or comment out the "#define DEBUG" and all of that code will be deactivated on the next recompile.

This is a derivative of a method I've used when coding in C, and if there was an inbuilt macro method for LSL I would probably have used it, but again, it's expecting a bit too much knowledge from the target audience, who may not know about pre-processors in the first place.

Many of the help requests we see on the forums are from people who have picked up  script that has escaped from Freebies Dungeon or Yadniis Junkard, and are trying to tweak it. They are bypassing the traditional scripting tutorials in favour of starting off with something that works, sort of, (and I have to admit I am exactly this sort of person myself).

We have to assume they know nothing of LSL functions not already present in the script and probably don't even know about the LSL portal. They need to be shown short succint examples of what to do, because they are hell-bent on completing their project and will b reluctant to be sent off on missions to learn and understand something that has no immediate chance of solving their problem.

We could respond to their requests by insisting they learn to code from the bottom up first, research the LSL portal before asking for help, etc. I don't think those are fruitful paths for either them or us. I am amazed at just how much I personally have learned over the past nine months after deciding t try and help a couple of newcomers who were determined to learn things their way rather than the prescribed way. If it means anything to those reading this, I found myself in the position described by Erasmus in Anathem when he realised that he should try and help Barb to understand the universe, and I have noticed the benefits much as Erasmus did.

  • Thanks 1
Link to comment
Share on other sites

40 minutes ago, Profaitchikenz Haiku said:

Many of the help requests we see on the forums are from people who have picked up  script that has escaped from Freebies Dungeon or Yadniis Junkard, and are trying to tweak it. They are bypassing the traditional scripting tutorials in favour of starting off with something that works, sort of, (and I have to admit I am exactly this sort of person myself).

We have to assume they know nothing of LSL functions not already present in the script and probably don't even know about the LSL portal. They need to be shown short succint examples of what to do, because they are hell-bent on completing their project and will b reluctant to be sent off on missions to learn and understand something that has no immediate chance of solving their problem.

I completely agree with this, and I don't think "copy this global variable and new function into your script" is a particularly helpful first step in teaching them how to figure out what's wrong. The whole premise of "properly debugging your code" is an advanced topic beyond complete beginners.

Edited by Wulfie Reanimator
  • Like 3
Link to comment
Share on other sites

Yeah. Sure it's more efficient to use preprocessor directives (and we might do that with our favorite IDE even if it's not the Firestorm thing) I think about the most we could hope to suggest to posters is one level of indirection from good ol' llOwnerSay(), just a function call, something like the Dout @Profaitchikenz Haikusuggests, so there's just that one function definition to change to turn debugging on or off, route output to a different channel, send it to email, etc.

For those with programming experience, we could suggest something like "verbose", either a debug "level" or a bitfield to select individual debugging flags, but if they're that sophisticated they'll have already adopted their favorite method with no prompting from any of us.

  • Like 2
Link to comment
Share on other sites

3 hours ago, Rolig Loon said:

That's a nice suggestion for those who use Firestorm, but I don't think we want to assume that newbies will be using it (or that we seem to be recommending that they do).

yes. The recommendation has to work for any viewer including the standard Linden viewer out-of-the-box

i agree with the suggestions that the first post should be stripped right down to the basics. The intended audience is people new to scripting. Then each successive post (which anyone can post) can introduce additional debugging features and techniques. With the understanding that should the reader never get beyond the first post they still have something useful to work with in any situation out-of-the-box

i think that we should try here to come to a consensus on what that first post should contain. It needs to be simple, straightforward and plain speaking to the intended audience. Knowing that successive posts will be speaking to a wider audience

  • Like 2
Link to comment
Share on other sites

4 hours ago, Ariu Arai said:

As nice as it is, it makes code look like a mess when viewed with a non-firestorm viewer. I don't use firestorm, but on occasion I've seen firestorm scripts with a whole separate copy of the code commented at the top, which is quite confusing to explain to someone who doesn't get why that would be there. (I changed XYZ on line abc (within the comment,) why didn't the thing work differently)

  • Like 1
Link to comment
Share on other sites

26 minutes ago, Mollymews said:

i think that we should try here to come to a consensus on what that first post should contain. It needs to be simple, straightforward and plain speaking to the intended audience. Knowing that successive posts will be speaking to a wider audience

Exactly.   Here's a possible text ....

If you are getting unexpected results from your script, it is often helpful to use the llOwnerSay function to report helpful information to you in chat.  For example,  suppose you have a loop in your script that is supposed to be changing selected values in a list that you have called lAll_my_values, but the script seems to be changing the wrong elements, or maybe adding the wrong amount to them ....

integer i;
while ( i < 12 )
{
    integer iMy_old_value = llList2Integer(lAll_my_values, i);
    llOwnerSay(" i = " + (string) i + "    iMy_old_value = " + (string) iMy_old_value);   // Report old values each time we enter the while loop ....
    if ( iMy_old_value > 37 )
     {
         lAll_my_values = llListReplaceList(lAll_my_values, [ iMy_old_value + 5], i, i);
         llOwnerSay("List Updated:  i = " + (string) i + "    New value = " + (string) (iMy_old_value + 5) );   // Report the new value if this one has changed ...
     }
     ++i;
}
llOwnerSay( "Here's the updated list ... " + llList2CSV(lAll_my_Values));

As in this example, place llOwnerSay statements wherever you want to verify the current values associated with variables of interest to you.  Make your messages as informative as possible.  Remember that llOwnerSay expects you to provide its message as a string variable, so you may need to typecast some information to (string), as this example illustrates.

Edited by Rolig Loon
  • Like 2
Link to comment
Share on other sites

37 minutes ago, Rolig Loon said:

Here's a possible text ....

i like this as a first post.  A plain example of using only llOwnerSay to show feedback messages

roll it right back to the beginning

and then in subsequent posts can introduce DEBUG like wrappers.  I take on board Wulfie's thought about this. That while a DEBUG wrapper can appear pretty straightforward to the more experienced scripters, this is not true for a person new to scripting

  • Like 1
Link to comment
Share on other sites

On 10/25/2021 at 10:12 AM, Profaitchikenz Haiku said:

Dout( string tellMeAbouIt)
{
    if(debug) llOwnerSay(tellMeAboutIt);
}

Interesting. That's much like a debug macro I made many years ago called "BLAT" for C & C++ programs that looks like:

#ifdef __cplusplus
   #ifdef BLAT_ENABLE
      #define BLAT(X) std::cerr << X << std::endl;
   #else
      #define BLAT(X)
   #endif
#else
   #ifdef BLAT_ENABLE
      #define BLAT(...) fprintf(stderr, __VA_ARGS__);
   #else
      #define BLAT(...)
   #endif
#endif

That's in C-preprocessor language, if you're not familiar with it. Does about the same in C & C++ as your Dout does in LSL. To use it, I sprinkle BLAT throughout my code, printing various stuff for debugging, then have a line at the top of each files that says either "#define BLAT_ENABLE" or "#undef BLAT_ENABLE".

Link to comment
Share on other sites

On 10/25/2021 at 10:12 AM, Profaitchikenz Haiku said:

Dout( string tellMeAbouIt)
{
    if(debug) llOwnerSay(tellMeAboutIt);
}

If you don't mind, I think I'll steal your idea and start using it in my own code, but rephrased a bit:

// some-damn-script.lsl

integer debug = TRUE;
string arrrgh = "apple";

BLAT (string text)
{
    if (debug) {llOwnerSay(text);}
}

my_func (integer Fred, key id)
{
    ... do stuff that might change arrrgh ...
    // What is arrrgh NOW???
    BLAT("About to return from my_func; arrrgh = " + arrrgh)
}

Saves having to do Ctrl-F "llOwnerSay" and delete dozens of lines of code, only to have to put them all back in later if more trouble happens; just keep flipping debug between TRUE and FALSE.

Link to comment
Share on other sites

3 hours ago, LoneWolfiNTj said:

That's in C-preprocessor language,

I am, and this technique comes from there and various Macro assemblers such as Macro11 on the early machines I started with. Up until Coffee's advice that each function consumes 512 bytes regardless of how little there is in it, I was using this technique quite extensively, to control various aspects of program behaviour via a few functions and some global variables. 

Rolig made a point in another thread after examining code that somebody was asking for help with, that the chatter was making it impossible to see what was going on, and this is going to have to be addressed later in the sticky, how to selectively output diagnostics. I have used a method much as described above, where debug has a value, and by increasing it more and more routines start hollering.

However, thinking through what others have posted here, I agree tht for a newcomer, this is a bit too esoteric, given that what they really want to do is get their script to work, not learn the intricacies of LSL.

The other issue I think we are going to have to put in a sticky is how to track down incorrect pairing or braces, not just one isn't there, but what happens when you have got them matched but not are the right places.

I wouldn't want to see a mass of stickied threads at the top of the forum so I would rather like such advice to be collected into a single topic.

Link to comment
Share on other sites

You are about to reply to a thread that has been inactive for 921 days.

Please take a moment to consider if this thread is worth bumping.

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now
 Share

×
×
  • Create New...