Jump to content

Organizing Code


Dourden Blindside
 Share

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

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

Recommended Posts

Hi everyone,

 

I'm new to scripting and i would like to ask a question about how to organize code. Personally i dislike a high number of global vars or having one event that handles multiple operations (like dataserver), so im thinking to use multiple scripts that do small pieces of code. I know the first reply would be that idle scripts consume memory, but do they still consume memory if i stop them (llSetScriptState)?

For example, have one script to return avatar key, another to return avatar profile picture, etc. They all send messages to my mains script which then stops the other scripts. Is this approach still bad from a performance/memory consumption point of view ?

 

I also looked into organizing my code based on states like:

- state "get_ava_key"; state "get_ava_profile_pic",  etc but...it still forces me to use global vars...like query id...and i personally dislike that. Also i loose the parallel processing of using multiple scripts...

 

What is your opinion?

 

Thank you,

David.

 

 

Link to comment
Share on other sites

Formal or beautiful LSL will only get you so far before you run out of script space. There is only 64kb for the whole shooting match, that takes absolute priority.

For example; Function are allocated in 512b blocks, which makes small functions expensive - in-lining the code is often smaller.

Micro optimizations quickly add up.

Bitmasks are magic.

Use notecards as read only memory.

Every byte is precious.

Don't use multiple scripts when one will do, if it wont fit, try harder.

Comments are free. Write more comments than code and do your future self a solid.

  • Like 10
  • Thanks 1
Link to comment
Share on other sites

I'm not sure why you might want to avoid either global variables or events that handle multiple operations.  Those two features generally save you time and memory, and they will give you much easier code to work with than having a flock of separate scripts communicating with each other.   I would avoid the having extra scripts unless you have no other options.

  • Like 4
Link to comment
Share on other sites

Firstly, insert repeat here of what Coffee Pancake said. I'm a semi-retired professional computer programmer myself, so I can vouch from experience that what she said is all true, and most of it not just in LSL, but in other languages as well (C, C++, Perl, Python). (The parts about being limited to 64KB is LSL-specific, but the rest isn't.)

Secondly, in LSL, I avoid non-default "states" (in the LSL sense) unless my object actually is going to be in more than one "states" (in the English sense) in which it will handle events (listen, dataserver, etc) differently for each "state". (This mirrors my approach to OOP: even if programming in an OOP-oriented language such as C++, unless the application actually does involve a hierarchy of classes of objects, I don't use OOP, I just use structured programming.)

Thirdly, regarding global variables, that choice is simple: I only make  a variable global if I need to use it in more than one function. (And here, I'm including LSL's built-in event handlers "listen", "dataserver", "timer", etc, as being "functions", because they actually are, even though LSL doesn't call them that.) Global variables serve as "liaisons" between functions. If you find yourself using a global variable in one function only, move it's definition to the inside-top of that function, so that it becomes local. This perhaps doesn't matter quite as much in LSL (where scripts must be kept short because total memory usage is limited to <= 64KB), but it's a good habit to learn if you ever intend to program in other languages. (And why shouldn't you? If you learn LSL, then I advise, also go learn Perl as well. Windows + Cygwin + Notepad++ + Perl is very useful for home computer system management.)

Fourthly, regarding comments, don't just write a lot of comments, actually write the comments and state names and event-handler names first, then fill-in the details later. In other words, don't start writing your program in LSL until you've written in it English (or other language you read&write fluently) first:

// Local-Radio.lsl
// Script for a "media-on-a-prim" radio.
// Written Feb 7, 2022, by Jack Sprat

// Global variables (inter-function liaisons):

// Functions:

// Reset the radio (re-read notecard "Radio Stations"), 
// converting lines of the form "name = URL" into entries
// in separate "station_names" and "station_urls" lists,
// or perhaps use a single strided list:
reset_radio() 
{
    ;
}

// Set radio to play a particular station:
set_media()
{
    ;
}

// Process a line of text from notecard "Radio Stations":
process_next_nc_line(string line)
{
    ;
}

// Our menu will need many pages, so write a function that
// will populate a given page number with appropriate buttons
// (current page is stored in "page", and number of pages is
// stored in "pages"):
menu_page()
{
    ;
}

// We only need the "default" state for this project:
default
{
    // Reset radio when script is compiled or reset:
    state_entry()
    {
        ;
    }

    // Reset radio whenever it's rezzed:
    on_rez(integer s)
    {
        ;
    }

    // Reset radio on inventory change:
    changed(integer change)
    {
        ;
    }

    // Read lines of text from notecard "Radio stations:
    dataserver(key query_id, string line) 
    {
        ;
    }
    
    // Launch menu whenever someone clicks radio:
    touch_start(integer touchNumber)
    {
        ;
    }

    // Listen for menu button presses:
    listen(integer channel, string name, key id, string message)
    {
        ;
    }
}

Then it just becomes a matter of translating your English text into LSL. This will involve writing code inside the functions, making lots of LSL built-in-function calls, creating local and global variables you need on-the-fly, etc. (Suggestion: try making a local radio by fleshing-out the details of the vaporware script I pasted above.)

I find that the official LSL wiki portal is a very helpful reference for LSL programming, so I put links to these pages on my Firefox Favorites bar:

Category:LSL Functions - Second Life Wiki

Category:LSL Events - Second Life Wiki

Category:LSL Constants - Second Life Wiki

That way, looking-up any technical details I need is only 1 click away.

And, finally, if you code doesn't work the first time you run it (and usually, it won't), then pepper your code with lots of llOwnerSay() function calls, printing the values of relevant variables so that you can trace problems back to their sources. There's no IDE, debugger, or breakpoints in LSL programming, so llOwnerSay() becomes your Best Debugging Buddy and should be used heavily for that purpose.

  • Like 4
  • Thanks 4
Link to comment
Share on other sites

3 hours ago, Dourden Blindside said:

Hi everyone,

 

I'm new to scripting and i would like to ask a question about how to organize code. Personally i dislike a high number of global vars or having one event that handles multiple operations (like dataserver), so im thinking to use multiple scripts that do small pieces of code. I know the first reply would be that idle scripts consume memory, but do they still consume memory if i stop them (llSetScriptState)?

For example, have one script to return avatar key, another to return avatar profile picture, etc. They all send messages to my mains script which then stops the other scripts. Is this approach still bad from a performance/memory consumption point of view ?

 

I also looked into organizing my code based on states like:

- state "get_ava_key"; state "get_ava_profile_pic",  etc but...it still forces me to use global vars...like query id...and i personally dislike that. Also i loose the parallel processing of using multiple scripts...

 

What is your opinion?

 

Thank you,

David.

Since you are new at scripting i kind of suggest you don't use states at all unless you have a good reason to (which in your example is not the case). Functions are the way to go (as in the sample of LoneWolfiNTj). Depending on how big your script will be (and if you run out of memory) you may use multiple scripts. You however don't want to use multiple scripts for small functions. Less scripts is better. You generally only work with multiple script when you need more memory or if you are dealing with functions that have a delay.

An additional thing to keep in mind with your example is that with both "get_ava_key" and "get_ava_profile_pic" a delay is to be expected. So the question would be when do we request this data and when do we need this data.

Having function scripts do make sense do, but if you use them just make one. For example one script that does all the dataserver stuff like loading notecard, getting key, profile pic, etc. Often having more scripts can result in a much better performance. It however all depends on the script. Don't be guided by the common misunderstanding that more scripts is bad or that scripts are to blame for lag. One single mesh tree on a sim has more impact on performance then 20 scripts.

Edited by bobsknief Orsini
typos
  • Like 1
  • Thanks 1
Link to comment
Share on other sites

some thoughts

the Firestorm preprocessor can help with organising source code. Useful for inlining code snippets saved in secondary source code files

states are most useful when we don't overdo them. A good use is when we want to initialise our application.  A common initialisation is reading from a notecard one time. Example:
 

default
{
   ... read from notecard ...

   ... OnInitialiseComplete
      state main;
}

state main
{

}

 

with multiple scripts then I think we need a compelling reason for doing this. A compelling reason can be a multi-avatar permissions application.  A large in memory database in a second script, data pushed/pulled by our main script using link messages

 

  • Like 2
Link to comment
Share on other sites

insofar as editors go, get used to using an external script editor. Once projects grow, the built in editor really becomes a handicap and can actually make it very difficult to debug large projects.

Personally, I really like VS Code for this (on a portrait monitor), it runs on everything, has lots of extensions (inc LSL syntax highlighting), can be customized out the wazoo, is aggressively maintained and developed, and will serve you well whatever you're developing not just LSL. At this point I use it for practically everything.

https://code.visualstudio.com/

There is even a browser based version so you can use it places you might not have permission to install or run your own copy (although this wont work from the SL client!)

https://vscode.dev/

Edited by Coffee Pancake
  • Like 1
  • Thanks 1
Link to comment
Share on other sites

Hi all,

First of all thank you for the numerous responses. I am still reading the replies the 100th time .

 

Functions

I see here a lot of thumbs up for functions. Ok, i will beat my brain around this and try to follow an organized style with it. I like the organization of @LoneWolfiNTj

 

Multiple Scripts

All i want is to keep my code clean, reusable and scalable. I thought that if i leverege llMessageLink i could create general/scalable scripts that i would later on reuse.

Yes i can copy paste the code, but...doesn't feel the same.

I don't want my scripts to run forever, i would kill them as soon as their purpose has been served. 

 

 

States

Honestly, i thought that states are the flow logic of my code. The rest are events.

Maybe I'm wrong, maybe i didn't understand the purpose of states. I thought it was similar to methods in OOP .No ?

 

External Editors

Painful subject, hurt my fingers trying to get eclipse to work with LSLForge. As soon as i did that, i found out that LLRequestUserKey is not working. I cried so much.

@Coffee Pancake how do you use VS with LSL ? I didn't know there's a plugin for that...or what am i missing.

I thought the only editors that kinda work are LSL Forge with Eclipse and LSLEditor.

 

Conclusion

Guess the more i'll code the more i'll understand your opinions. So far scripts i read are usually just a sequence of event handlers without anything semantic in it.

 

Again, THANK YOU!

 

 

Link to comment
Share on other sites

The biggest help I found in organizing scripts was using the LSL Preprocessor found in the Firestorm viewer. I don't use the lazy lists or switch statements, as those don't offer any advantages other than making things easier to type. However, the "constants" and includes are extremely helpful.

If you have several functions that you don't want to have to copy-and-paste into each script, just put it into an external text file on your PC and put #include "MyFunctions.txt" into your script. Then #define DoTheThing to add that function to your script. I've found it extremely helpful for easily enabling debug mode in scripts. Just uncomment "#define DEBUG" and recompile and suddenly all of the debug code is enabled.

  • Thanks 1
Link to comment
Share on other sites

asdf

36 minutes ago, Dourden Blindside said:

maybe i didn't understand the purpose of states. I thought it was similar to methods in OOP

Nope. "States" in LSL are modes of being.

In First Life (the real world), your car has these states: stopped_engine_off, stopped_engine_on, moving_reverse, moving_forward.

Likewise an object in Second Life can have multiple states. For example, my security orb has the states "default", "off", and "on". And it only spends a fraction of a second in "default" when the script is first started, then spends all of its time in "off" or "on". I do this because the two states have different requirements as to how events are handled. (Eg, if "off", the orb has no timer running, but if "on" it does.)

For more information on states, google for "finite state machines".

33 minutes ago, Dourden Blindside said:

So far scripts i read are usually just a sequence of event handlers without anything semantic in it.

All of the semantics are in the event handlers. Even external functions have to be called from within event handlers. That's because, like with Windows GUI programming, everything in LSL is event-driven. Once the "default state_entry()" is executed (first few microseconds), an LSL script spends all the rest of its time waiting for "events" to occur. Such events may be "next timer clock tick", or "someone clicked the object", or "someone pressed a button on a dialog box", or "this script received a message from another script on a comm channel", or "an avatar just granted permission to be teleported", or many others. I recommend studying every event on the "events" page of the LSL wiki to get a good idea of what they all do, because events are the heart of LSL:

Category:LSL Events - Second Life Wiki

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

24 minutes ago, Dourden Blindside said:

So far scripts i read are usually just a sequence of event handlers without anything semantic in it.

LSL is an event-driven language. Event handlers are the semantics; everything else is syntactic sugar.

Functions are expensive, as @Coffee Pancake says, but not nearly as expensive as running multiple scripts. As long as a script is resident in a rezzed object it uses sim memory; if it's explicitly stopped, it will keep using memory (although it will annoyingly lose state values if the region restarts while the script is stopped), but it will go off the scheduler queue, so that reduces sim lag on script-bound regions—until it gets started again, which incurs a bunch of overhead, so it's a bad idea to stop a script unless it'll be idle for some time. And again, each running script incurs some overhead whether it has anything to do or not, and linked_message communication between scripts is (even) slower than function calls.

Generally suppress any consideration of "parallel processing" with multiple scripts. Scripts in the same region run in a single scheduled thread; multiple scripts may "cheat" so each gets a chance at reaching the front of that queue per simulation frame, but that's only rarely worth the communications overhead. Some built-in function delays, however, are still practical to defeat using multiple service scripts.

Scripts that use states can almost always be made more efficient by replacing the states with a global variable and some conditionals. The main exception is, as @LoneWolfiNTjsays, if different events have different relevance to the script part of the time, so segregating those handlers to a separate state can make sense (and, in the case of touch-related handlers, manage the appearance of the mouse cursor). They can also be handy for closing a bunch of open listens all at once, or for state_entry / state_exit clean-up.

  • Like 2
  • Thanks 1
Link to comment
Share on other sites

13 hours ago, Coffee Pancake said:

Function are allocated in 512b blocks, which makes small functions expensive - in-lining the code is often smaller.

 

Fascinating, I knew states grabbed chunks of memory but didn't realise that functions were granular in that way.

 

For the OP, simplicity is the key, and that doesn't mean factorising out the code, sometimes linear-flow code with if-else or even gotos often works just as well, the main criteria from your perspective is, can you keep track of your own code?

The main advantage to several scripts is that developing and testing can be a lot easier with smaller chunks and a few test-harnesses, once working, you can combine them into a single script.

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

2 hours ago, Dourden Blindside said:

@Coffee Pancake how do you use VS with LSL ? I didn't know there's a plugin for that...or what am i missing.

I thought the only editors that kinda work are LSL Forge with Eclipse and LSLEditor.

Change debug setting ExternalEditor to "C:\Program Files\Microsoft VS Code\Code.exe" "%s" 

(adjust as required for your editor wherever it may be installed)

The work flow is now, open a script in the viewer, make the script edit floater nice and small (you still need to see it for errors as LSL is compiled on the server, not locally), press the "Edit..." button on the script floater, the script will open in your external editor and reset in world. When you save your script in the external editor, the script in SL will update, save and get recompiled.

Install the VSCode LSL extension in VS Code

vpLIWZO.png

Optionally, download LSLint from https://github.com/FixedBit/lslint/releases/ unzip and place the exe somewhere in your path and install LSLint for VSCode

ysRHWzt.png

This will catch things in the editor and nag you with little popup notification messages (It's handy .. if clunky, the popups are notifications rather than a list).

wyNPIEP.png

  • Like 1
  • Thanks 2
Link to comment
Share on other sites

4 hours ago, Dourden Blindside said:

Multiple Scripts

All i want is to keep my code clean, reusable and scalable. I thought that if i leverege llMessageLink i could create general/scalable scripts that i would later on reuse.

Yes i can copy paste the code, but...doesn't feel the same.

I don't want my scripts to run forever, i would kill them as soon as their purpose has been served. 

LSL scripts don't scale, that's the problem. They don't scale as individuals and even worse once you start splitting them up, because of problems with script-to-script* communication. A couple thousand active scripts in a sim will eat up any spare time it might hope to have.

Turning them off with llSetScriptState only gets rid of their CPU load, but they preserve state so you're not getting rid of the memory-usage.

*script-to-script communication is generally done with llMessageLinked if the scripts are in the same object. Its caveats are pretty severe in practice.

If you just want clean and reusable code, the LSL preprocessor and #include are your best bet. But considering the event-based language (and lots of globals as a result), it's difficult to include any code that isn't strictly self-contained. (If your code needs to be in specific event(s) or requires a global, things get awkward to manage.)

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

38 minutes ago, Dourden Blindside said:

I will start to understand what  states mean: "denial, depression, acceptance, bargaining" etc. 

I think the general theme of this thread, is that to really get better at LSL, and programming in general, you need to step back from a 'semantic' approach and think more pragmatically.

While LSL is a "high level language" in the sense that it isn't direct assembly, it's fairly "low level" in that it does not hold your hand with many high level concepts. (structs, dictionaries, anonymous functions etc etc.)

States don't mean anything, other than what they do: If a script is in state A when an event happens, state A's event handler is triggered (if it has one). Compare two examples:

(quoted for collapse-ability)

Quote

 

string say;

default
{
  state_entry()
  {
    llListen(0,"",llGetOwner(),"");
    say = "Please say my name!";
    llSay(0,say);
  }
  listen(integer chan, string name, key ID, string text)
  {
    llSetObjectName(text);
    state two;
  }
}
state two
{
  state_entry()
  {
    llListen(0,"",llGetOwner(),"");
    say = "Please touch me!";
    llSay(0,say);
  }
  touch_end(integer i)
  {
    state three;
  }
}
state three
{
  state_entry()
  {
    llListen(0,"",llGetOwner(),"");
    say = "Please say my text!";
    llSay(0,say);
  }
  listen(integer chan, string name, key ID, string text)
  {
    llSetText(text,<1,1,1>,1.0);
    //state four;
  }
}
// alternate implementation

string say;
integer istate = 0;

default
{
  state_entry()
  {
    llListen(0,"",llGetOwner(),"");
    say = "Please say my name!";
    llSay(0,say);
  }
  listen(integer chan, string name, key ID, string text)
  {
    // putting the constant first is a good trick to prevent = vs == errors:
    if(0 == istate)
    {	llSetObjectName(text);
     	say = "Please touch me!";
    	llSay(0,say);
     	++istate;
    }else if(2 == istate)
    {	llSetText(text,<1,1,1>,1.0);
     	say = "Done!";
    	llSay(0,say);
     	 //++istate;
    }
  }
  // touch end is reccomended for state changing stuff.
  touch_end(integer i)
  {
    if(1 == istate)
    {
      say = "please say my text!";
      llSay(0,say);
      ++istate;
    }
  }
}

 

Both do the basic job of asking the user for a name, then get a touch input (this is a toy example, assume an actual thing would do something interesting with the touch like determining what button on the object was pressed), and then a settext. For applications where the thing that needs to be done to get to the next step is roughly the same (say 20 questions, replying yes or no) you'd probably want to go wiht the second, non-state approach, but if the kinds of things are wildly different (say some text, drop something into the prim, run in a circle around the prim) the first state-based approach might work better.

The point is there are two valid methods of doing basically the same thing (wait for something to happen then after that thing happened, wait for something different to happen) and neither is more "Correct" than the other without a specific context. States are a pragmatic tool, they don't necessarily "represent" anything.

  • Like 1
Link to comment
Share on other sites

18 hours ago, Dourden Blindside said:

States

Honestly, i thought that states are the flow logic of my code. The rest are events.

Maybe I'm wrong, maybe i didn't understand the purpose of states. I thought it was similar to methods in OOP .No ?

Kind of. You could compare it to a "Class" with functions. It however doesn't persist after moving to a other state.
Therefore its not really that useful since every time you move to a other state all data from the previous state is gone (unless you store it in global variables).
Better is to just use functions or functions with functions. Ex: string some_data = funcion_abc("var", "var");

You could of course create a "OOP Class" script from which you request data with a link message. This however doesn't really have much benefit over a script with a bunch of functions (using just functions you end-up with a lot less and cleaner code).

One other thing to keep in mind is that when you use link messages you get a delay. So again shoving all functions in the same script is a lot more practice.

Link to comment
Share on other sites

States are best thought of as a way to have 2 very different scripts in the same script, however due to the memory overhead the practical use cases are limited.

A simple example would be a lamp that could be rezzed or held. 

Most examples that use states do so unnecessarily, eg an object that has a loading and running phase, and there is no downside to just merging both states into a single one.

I rarely find them to be necessary or beneficial, they certainly shouldn't be thought of as OOP classes.

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

48 minutes ago, Coffee Pancake said:

Most examples that use states do so unnecessarily, eg an object that has a loading and running phase, and there is no downside to just merging both states into a single one.

I rarely find them to be necessary or beneficial, they certainly shouldn't be thought of as OOP classes.

I agree and, in general, I prefer not using states unless I can't think of a smoother way to do things.  One advantage of using states, though, is that they provide a simple way of chopping of repeating sensors, listeners, and a number of other things, so you can be sure that execution has passed into a new realm where you won't be getting triggers from actions that you no longer care about.  The downside, of course, is that information that you do want to have access to needs to be in global variables and possibly handled in user-defined functions.  That can mean carrying a lot more baggage at the top of a script, and then remembering where all those global variables might be updated.

  • Like 4
Link to comment
Share on other sites

On 10/25/2021 at 7:07 PM, Coffee Pancake said:

Function are allocated in 512b blocks

How LSL memory is allocated in Mono is a mystery to me. Here's a dopey sample script to play with:

//string GLOBAL_VARIABLE;

fun() {}

default
{
    state_entry()
    {
        llOwnerSay((string)llGetUsedMemory());
        //fun();
    }
    touch_start(integer total_number)
    {
        state dead; 
    }
}

state dead
{
    state_entry()
    {
    }
}

Yeah, it doesn't do anything so the function and state definitions might get optimized-out in some cases, but that doesn't seem to be where the weirdness arises.

  1. As shown, it'll report 3364 bytes memory used.
  2. Comment out the function definition, the state definition, the state invocation, even the whole touch_start event handler: still 3364 bytes used, so maybe that's just the floor for a script.
  3. With all that commented out, add the GLOBAL_VARIABLE definition: 3386 bytes, up by 22, so that's plausible.
  4. Back to the original version with the GLOBAL_VARIABLE commented out, but this time with the function call enabled: 3876, up by 512 from original.
  5. Put back the GLOBAL_VARIABLE definition: 3898, adding back those 22 bytes again
  6. Comment-out the function call so the script is back to where it was at step 3, but the memory reported is still 3898.
  • Thanks 1
Link to comment
Share on other sites

20 hours ago, Rolig Loon said:

One advantage of using states, though, is that they provide a simple way of chopping of repeating sensors, listeners, and a number of other things

yes is quite a few cases where states make sense for the reasons you mention

a smart house fr example that does stuff on presence of the home owner

default
{
  state_entry()
  {
      llSensorRepeat("", llGetOwner(), AGENT, 64.0, PI, 60.0);
  }
 
  sensor(integer num_detected)
  {
      state ownerAtHome;
  }
}

state ownerAtHome
{
  state_entry()
  {
      // .. unlock the doors
      // .. turn the lights on
      // .. start the fireplace burning
      // .. put the kettle on
      // .. open the curtains
      // .. turn on the radio/tv
      llOwnerSay("Welcome home! " + llGetDisplayName(llGetOwner()));     
      llSensorRepeat("", llGetOwner(), AGENT, 64.0, PI, 300.0);
  }
 
  no_sensor()
  {
     state default;
  }
 
  state_exit()
  {
      // ... lock the doors
      // ... turn the lights off
      // ... douse the fireplace
      // ... turn the kettle off
      // ... close the curtains
      // ... turn off the radio/tv
      // ... turn off the shower if is running
  }
}

 

 

  • Like 2
Link to comment
Share on other sites

You are about to reply to a thread that has been inactive for 961 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...