Difference between revisions of "How to make writing LSL scripts easier"

From Second Life Wiki
Jump to navigation Jump to search
Line 578: Line 578:
[http://gcc.gnu.org/onlinedocs/cpp/ here].
[http://gcc.gnu.org/onlinedocs/cpp/ here].


Imagine you want to create a script that figures out which agent is
Imagine you want to create a script that figures out which agent on
the furthest away from the object the script is in and uses keyframed
the same parcel is the furthest away from the object the script is in
motion to move the object towards this agent when the owner of the
and moves the object towards this agent when the owner of the object
object touches it.
touches it.


The source of the script, written to be preprocessed with cpp and with
In the following, the source for the script will be developed piece by
line numbers added for reference, looks like this:
piece.  You can find the complete source in the
[https://github.com/Ratany/lsl-repo git repository that accompanies this article]
in "projects/example-1/src/example-1.lsl".
 
Obviously, the script will need a touch event, a way to find out which
agent is the furthest away and a a way of moving to the agent.
 
In this case, [[LlSetKeyframedMotion|keyframed motion]] (KFM) is a
good way to get the object moving: It doesn´t require the object to be
physical and thus lets it move through obstacles like walls.  KFM
requires the object to use the so-called "prim equivalency system",
which means that it must either be mesh, or the physics type of one of
its prims must be either "convex hull" or "none".  This can either be
set in the editing window when editing the object, or by the script.
The physics type of the root prim cannot be "none", and since it´s
hard to say whether "none" or "convex hull" is better for a particular
object, the script will simply use "convex hull".  Since the physics
type of the object needs to be set only once, the script should use a
state_entry event to set it.  It can be set with
[[LlSetLinkPrimitiveParamsFast#llSetLinkPrimitiveParamsFast|llSetLinkPrimitiveParamsFast()]].
 
Finding out which agent is the furthest away from the object requires
to find out what agents there are.  A sensor is unsuited because the
agent could be out of sensor range, and the sensor would report no
more than the closest sixteen agents.  Using
[[LlGetAgentList|llGetAgentList()]] works much better for this.  It is
also necessary to find out what the distances between the agents and
the object are, for which [[LlGetObjectDetails|llGetObjectDetails()]]
can be used.
 
The object should move to a position at "some distance" from the
agent.  It seems nicer to use a random distance rather than a fixed
one.  This can easily be done by creating a "random vector", i. e. a
vector the components of which are randomized.  What
[[LlFrand||llFrand()]] returns is not really a random number, but it´s
sufficient for this purpose.
 
As with all sources, I want to use some of the defines that are
gathered in my "lslstddef.h".  One of them is the macro "RemotePos()".
It is a shortcut for using llGetObjectDetails() to find out the
position of and object or prim or agent.  Another one is "SLPPF()", a
shortcut for llSetLinkPrimitiveParamsFast() because
llSetLinkPrimitiveParamsFast() is awfully long.
 
This makes for a source (example-1_A) that looks like this:




<lsl>
<lsl>
  1 #include <lslstddef.h>
#include <lslstddef.h>
  2
  3
  4 #define fUNDETERMINED              -1.0
  5 #define fIsUndetermined(_f)        (fUNDETERMINED == _f)
  6
  7 #define fMetersPerSecond          3.0
  8 #define fSecondsForMeters(d)      FMax((d) / fMetersPerSecond, 0.03)
  9
  10 #define fRandom                    (llFrand(3.5) - llFrand(2.5))
  11 #define vRandomDistance            (<fRandom, fRandom, fRandom>)
  12
  13
  14 default
  15 {
  16 event touch_start(int t)
  17 {
  18 if(llDetectedKey(0) == llGetOwner())
  19 {
  20 vector here = llGetPos();
  21
  22 list agents = llGetAgentList(AGENT_LIST_PARCEL, []);
  23
  24 #define kAgentKey(x)      llList2Key(agents, x)
  25 #define vAgentPos(x)      RemotePos(kAgentKey(x))
  26 #define fAgentDistance(x)  llVecDist(here, vAgentPos(x))
  27
  28 float distance = fUNDETERMINED;
  29 int goto;
  30 int agent = Len(agents);
  31 while(agent)
  32 {
  33 --agent;
  34
  35 float this_agent_distance = fAgentDistance(agent);
  36 if(distance < this_agent_distance)
  37 {
  38 distance = this_agent_distance;
  39 goto = agent;
  40 }
  41 }
  42
  43 unless(fIsUndetermined(distance))
  44 {
  45                                                    llSetKeyframedMotion([PosOffset(here,z,
                                                                              vAgentPos(goto)
                                                                              + vRandomDistance),
                                                                              fSecondsForMeters(distance)],
                                                                            [KFM_DATA, KFM_TRANSLATION]);
  46 }
  47
  48 #undef AgentKey
  49 #undef AgentPos
  50 #undef AgentDistance
  51
  52 }
  53 }
  54 }
</lsl>




You can find this source in the [https://github.com/Ratany/lsl-repo git repository that accompanies this article]
default
in projects/example-1/src/example-1.lsl.
{
event touch_start(int t)
{
// find furthest agent and move to them on touch by owner
}


This example is intentionally exaggerated to show a few things.
event state_entry()
{
// set the physics type
}
}
</lsl>




The first line includes another file, named "lslstddef.h", into the
The '#include' in the first line is a so-called preprocessor
source.  It is named with the extension .h to indicate that it is a
directiveIt tells the preprocessor to do somethingThere are
so-called header fileThe file is called header file because it goes
other directives, like "#define", "#undef", "#if", "#endif" and some
at the top of the sourceIncluding it with '#include <lslstddef.h>'
others.
works out the same as if the contents of the file "lslstddef.h" were
actually contents of the source.


'#include' is a so-called directive for the preprocessor to include a
"#include" tells cpp to find a file "lslstddef.h" within some directories it
file.
searches through by default and to make it so as if the contents of
this file were part of the source.  Suppose you have the source saved
as "example-1.lsl": The preprocessor includes "lslstddef.h" into
"example-1.lsl".  It´s output will not contain the first line because
the first line is a preprocessor directive.


The file name is written in pointy brackets here because cpp has been
The source ("example-1.lsl") is never modified by the preprocessor.
told where to look for the file.  This will be explained later. If
Including "lslstddef.h" makes everything in "lslstddef.h" known to the
"lslstddef.h" was in the same directory as "example-1.lsl", it would
preprocessor.  The preprocessor produces an output which is a copy of
be included with '#include "lslstddef.h"'.
the source with modifications applied to it as the directives, like
the ones in "lslstddef.h", demand.


You can find "lslstddef.h" in the [https://github.com/Ratany/lsl-repo git repository that accompanies this article]
"lslstddef.h" has a lot of directives for the preprocessor. Most of
in projects/include/.
them define something with "#define".  When something is defined with
"#define", it is a so-called define.  Macros can also be defined with
"#define".  Macros are called macros because they are defines that
take parameters.


"lslstddef.h" contains a lot of so-called defines, similar to line 4
in the source above.  Line 4 tells the preprocessor to replace
'fUNDETERMINED' everywhere in the source with '-1.0'.  This is like a
case-sensitive search-and-replace operation you could do yourself with
your favourite editor.


You may have noticed that in example-1_A, it doesn´t read
"touch_start()" but "event touch_start()".  That´s because I write my
sources like that: Putting "event" before events makes them easier to
find and the source looks nicer and is easier to read for me.  In your
sources, you can do it the same way or use something else than "event"
or just write "touch_start()" instead.  You can see what it looks like
on my screen in the example picture --- I currently use green on black
in emacs.  It sticks out, and "event" makes a good search string that
saves me searching for particular events.


If you wouldn´t use a preprocessor, line 4 might read
[[Image:Cpp-wiki-example-1_A.jpg|thumb|Easier to read]]


The LSL compiler wouldn´t compile the source, not only because it
doesn´t know what to do with "#include" directives, but also because
it doesn´t compile statements like "event touch_start()".  Therefore,
"lslstddef.h" defines that "event" is nothing:


<lsl>
  4 float fUNDETERMINED = -1.0;
</lsl>


<cpp>
#define event
</cpp>


You would be abusing a variable of the type float as a constant.
Since it´s a variable, you could have a typing error or other mistake
in your source which changes the value of the variable, like
'if(fUNDETERMINED = z) ...' instead of 'if(fUNDETERMINED == z)
...'. Such a typing error can be difficult to find.  This problem does
not occur when you use a preprocessor and define fUNDETERMINED as -1.0
because the mis-typed statement would read 'if(-1.0 = z) ...', and you
would get an error message when your script is compiled by the LSL
compiler.


In case you have modified your source so it doesn´t use fUNDETERMINED
By including "lslstddef.h", cpp knows this and replaces all occurances
anymore, it will not appear anywhere in your script when fUNDETERMINED
of "event" with nothing, making "event" disappear from the result.
was a define --- even when the definition itself is not removedWhen
This doesn´t mean that "event" is not defined.  It´s a define.  You
it was a variable, the variable will still be in your script and use
can use the directive "#ifdef" with such defines:
script memory until you remove its declaration, which you may forget
to do over all the modifications.




On a side note: A simple test has indicated that using a define
<cpp>
instead of a float-variable ''may'' save you 8 bytes of script memory.
#ifdef event
''May'' means that the test was so simple that optimisations done by
#define test 1
the LSL compiler can have produced misleading results.
#endif
</cpp>


Whether you do save script memory through using defines vs. variables
(and functions vs. macros) or not always depends on your particular
script.  Since it is easy to change a define into a variable (or
sometimes a function into a macro) or vice versa, this is easy to test
with your particular script.


It seems obvious that you would waste memory when you use a macro that
This would create a define named "test" only if "event" is defined.
the preprocessor turns into a lengthy expression which occurs in the
Much like cpp replaces "event" with nothing, it would now replace
script multiple times.  When it matters, test it; you may be
"test" with "1" because "test" is defined to be "1".
surprised.  In any case, you need to be aware of what the macros you
use turn into.  This will be pointed out in more detail later.




What is called a "preprocessor macro" is defined in line 5.  The
The next thing the example script needs is to set the physics
definition is a macro because, similar to a function, the define has a
type. The define "SLPPF" is a shortcut for
parameterMacros can have multiple parameters, and you can even use
"llSetLinkPrimitiveParamsFast", so use that.  The script also needs to
macros which have a variable number of parameters. Some of the macros
check if it was the owner of the object who touched it and do nothing
defined in "lslstddef.h" use a variable number of parameters.
when it was someone elseIt needs to know the position of the object
the script is in and what agents are around so it can figure out which
agent is the furthest away from the object. Add all this to the
source, which is now example-1_B:


The macro in line 5 instructs the preprocessor to replace all
occurances of 'fIsUndetermined' with '(fUNDETERMINED == _f)' and to
replace the paremeter '_f' with what was specified as a parameter to
'fIsUndetermined'.  For example, 'fIsUndetermined(5.0)' is replaced
with '(fUNDETERMINED == 5.0)'.


This wouldn´t be too useful because it would turn line 43 into
<lsl>
'unless(fUNDETERMINED == distance)'.  But the preprocessor doesn´t
#include <lslstddef.h>
stop there: 'fUNDETERMINED' was defined as '-1.0' in line 4, and the
preprocessor continues replacing stuff.  It turns line 43 into
'unless(-1.0 == distance)'.


In "lslstddef.h" is defined that 'unless(_cond)' must be turned into
'if(!(_cond))'.  Since "lslstddef.h" was included in line 1, the
preprocessor "knows" this and the final result of all the replacing is
'if(!((-1.0 == distance)))'.  And that is something the LSL compiler
can compile.


default
{
event touch_start(int t)
{
unless(llDetectedKey(0) == llGetOwner())
{
return;
}


Using 'unless' for LSL is, admittedly, ideosyncratic.  It comes from
vector here = llGetPos();
perl where it´s part of the language, and in perl, you can write stuff
list agents = llGetAgentList(AGENT_LIST_PARCEL, []);
like 'print 5 unless 5;', which will not print anything.  But where is
}
the fun when you can´t use a preprocessor to do ideosyncratic stuff?
Writing 'if(-1.0 != distance)' all the time can get boring.


Using 'unless' is particularly fun when you have to deal with several
event state_entry()
statements for the condition: 'unless((A > 5) || (B < 4))' probably
{
doesn´t do what you wanted.  Chances are good that you wanted
SLPPF(LINK_THIS, [PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_CONVEX]);
'unless((A > 5) && (B < 4))'.
}
}
</lsl>




More importantly, you might suddenly want to use a different value to
With
indiciate that something is undetermined.  Your script may require it,
or you might like it better.  Would you want to use -8874563.33356478
instead of -1.0, the only place you´d need to change your source is
line 4.




Macros can use other macros.  The macro 'fSecondsForMeters', defined
<cpp>
in line 8, uses the macro 'FMax'.  This is similar to functions that
#define event
call other functions.  ('FMax' is defined in "lslstddef.h" as an
#define SLPPF                          llSetLinkPrimitiveParamsFast
expression that evaluates to the greater of the two floating point
</cpp>
values supplied to 'FMax' as parameters, which is a function missing
from LSL.)




Line 10 and 11 give an overly simple example of how you can break down
in "lslstddef.h", the preprocessor replaces "event" with nothing and
logic into small pieces.  These lines don´t break down any logic, but
"SLPPF" with "llSetLinkPrimitiveParamsFast".
they show a concept: Define an operation or goal, then figure out
small steps and how to combine them in such a way that the desired
operation is being performed, or the goal reached. The size of a
single step can neither be greater, nor smaller than the magnitude of
the atomic operations the programming language allows for.  That´s
what programming is all about.


You can use a preprocessor to make smaller steps than you otherwise
would.  When you use it to break down logic, you are making things
easier than you otherwise would.  You can use a preprocessor to make
larger steps, by combining a number of small steps into a single,
large step.  When you use it to make larger steps, you are making
things easier than you otherwise would.  But are you?  When you use a
preprocessor carelessly, you may experience difficulties you otherwise
wouldn´t.  That´s the way a preprocessor works.


Anyway, llFrand() is an atomic operation in LSL.  You could write your
Lists in LSL can be a pita to work with.  You end up using functions
own algorithm to generate random numbers --- and you might be
to pick out list elements like [[LlList2Key|llList2Key()]]They
surprised how difficult that can beComputers producing true
combine with functions that return lists and lead to unwieldy
randomness, if there is such a thing at all, can usually be considered
expressions like
to be broken.


Creating a vector with some random values is not an atomic operation
in LSL.  If there was an atom of the language to create such a vector
--- like a function that does it for you --- you could use it.  Or you
could write your own algorithm to create random vectors.


Lines 10 and 11 represent an algorithm to create a random vector.  It
<lsl>
basically takes two steps:  Make a random number and put it into a
llList2Vector(llGetObjectDetails(llList2Key(agents, 0), [OBJECT_POS]), 0);
vector.  Since a vector consists of three numbers, you need three
</lsl>
numbers to make a vector.
 
So break that down into making a random number and into putting that
number into the vector.  The preprocessor puts these steps
together.  You could do it all at once and write 'vector random =
<llFrand(3.5) - llFrand(2.5), llFrand(3.5) - llFrand(2.5),
(llFrand(3.5) - llFrand(2.5)>'.  But that isn´t as easy to read as
lines 10 and 11, is it?


When code is easier to read, it´s easier for you to deal with it, and
it´s less likely that you make a mistake.  You´ll be more productive
because you don´t waste time trying to figure out code which is hard
to read, and creating LSL scripts becomes easier:  You are still
experiencing the same difficulties, but they are easier to overcome
when you have better tools at hand.


You can find a better example for breaking down logic in
which combine with other functions like [[LlVecDist|llVecDist()]] into
"geometry.lsl", which is in the
even longer expressions like
[https://github.com/Ratany/lsl-repo git repository that accompanies this article]
("projects/lib/geometry.lsl"):  The macro 'geom_nofsharedsegments', when used as
'geom_nofsharedsegments(A, B)', turns into:




<lsl>
<lsl>
((((A.x == 0.0) && (A.y == 0.0)) && ((B.x == 0.0) && (B.y == 0.0))) +
llVecDist(llList2Vector(llGetObjectDetails(llList2Key(agents, 0), [OBJECT_POS] ), 0),
((A.x < 0.0) && (B.x < 0.0)) + ((A.x > 0.0) && (B.x > 0.0)) + ((A.y >
          llList2Vector(llGetObjectDetails(llList2Key(agents, 1), [OBJECT_POS] ), 0));
0.0) && (B.y > 0.0)) + ((A.y < 0.0) && (B.y < 0.0)) + ((A.z > 0.0) &&
(B.z > 0.0)) + ((A.z < 0.0) && (B.z < 0.0)))
</lsl>
</lsl>




Try to maintain this code, maybe a year after you wrote it. The source
if you were to compute the distance between two agents.
simply reads:




<cpp>
Lists are sometimes strided, and then you also need to remember which
// number of segments shared by two points
item of a stride is to be interpreted as what.  Your code becomes hard
//
to write, hard to read and hard to modify.  You can use defines and
#define geom_nofsharedsegments(_p1, _p2)       (geom_cmpsegment(center, _p1, _p2) \
macros to avoid this.
+ geom_cmpsegment(left, _p1, _p2) \
 
+ geom_cmpsegment(right, _p1, _p2) \
The list used in example-1_B is not strided, simple and a local
+ geom_cmpsegment(front, _p1, _p2) \
variable.  Using a define and macros to make it useable is overdoing
+ geom_cmpsegment(back, _p1, _p2) \
it, but I want to show you how it can be done.
+ geom_cmpsegment(above, _p1, _p2) \
 
+ geom_cmpsegment(below, _p1, _p2)) \
You can put preprocessor directives anywhere into your source.  They
</cpp>
don´t need to be kept in seperate files.  Traditionally, they had to
be at the beginning of a line.  This isn´t required anymore --- but do
not indent them!  They are not part of your code.  They modify your
code.  You could see them as "metacode".


With example-1_B, four things can be defined to make the list of
agents easier to use:


It tells you what it does when you read it.  The source
"projects/rfedip-reference-implementation/src/rfedipdev-enchain.lsl",
which is in the
[https://github.com/Ratany/lsl-repo git repository that accompanies this article],
uses this.  You may want to check out [[LSL_Protocol/rfedip|this article]]
to learn more about it.


# a define for the stride of the list: "#define iSTRIDE_agents 1"


Lines 24 through 26 define some macros to make the list that was
# a macro that gives the UUID of an agent on the list: "#define kAgentKey(x) llList2Key(agents, x)"
declared in line 22 easier to use.  (Using three macros here is
overdone because it´s an example source.)


They seem to appear randomly somewhere in the source, but they have
# a macro that gives the position of an agent on the list: "#define vAgentPos(x) RemotePos(kAgentKey(x))"
been intentionally placed where they are: The scope of the list
declared in line 22 doesn´t go beyond line 52; it´s not a global
variable.  The macros are "deleted" in lines 48--50 because they are
not needed anywhere else.  Macros or defines with the same names and a
different or the same meaning could be used somewhere else in the
source.  The preprocessor is instructed to "forget" them with
the '#undef' directive.  It´s not required to undefine them.  It´s
tidy.


You don´t need to do it this way.  Traditionally, all preprocessor
# a macro that gives the distance of an agent from the object: "#define fAgentDistance(x) llVecDist(here, vAgentPos(x))"
directives --- and #define is one of them --- were required to be at
the beginning of the line and ignored by the preprocessor when they
were not and the C compiler would give you an error message.  This
isn´t required anymore.  It may look better to indent them with the
code.  By all means, don´t do that!
'''Preprocessor directives are not part of the code.'''
They modify code.  You could call them "metacode".  They are outside
of the code.  You do not want to overlook them.  You don´t want messy
code.  Or do you want to experience difficulites you otherwise
wouldn´t?


When you have a (longer) script that uses some global lists,
maybe even strided lists, exclusively using macros to operate with the
lists makes a lot of sense.


The code can be a lot shorter and easier to read:
Those will be used for a loop that iterates over the list.  This leads
to example-1_C:




<lsl>
<lsl>
vector chainpos = vChainPos(chain);
#include <lslstddef.h>
 


vector chainpos = llList2Vector(llGetLinkPrimitiveParams(llList2Integer(lChains, chain), [PRIM_POSITION]), 0);
default
</lsl>
{
event touch_start(int t)
{
unless(llDetectedKey(0) == llGetOwner())
{
return;
}


vector here = llGetPos();
list agents = llGetAgentList(AGENT_LIST_PARCEL, []);


mean both the same.  The first line tells you straight away what it
#define iSTRIDE_agents    1
is. The second one makes you ask yourself if you got it right.  In
#define kAgentKey(x)      llList2Key(agents, x)
your source, you may have many lines like that.
#define vAgentPos(x)      RemotePos(kAgentKey(x))
#define fAgentDistance(x) llVecDist(here, vAgentPos(x))


You may need to change the stride of a list.  Imagine you need to
int agent = Len(LIST_agents);
change a strided list so that the link number of the prim stored in
while(agent)
the list is no longer the first item of a stride but the second one.
{
agent -= iSTRIDE_agents;
}
}


Without macros, you have to go through your whole source, find all the
event state_entry()
ten or how many lines, and change all of them.  Perhaps you overlook
{
one of them and then have to waste an hour or so to figure out why
SLPPF(LINK_THIS, [PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_CONVEX]);
your script suddenly doesn´t work right anymore --- if you even notice
}
that it doesn´t work.
}
</lsl>


With macros, you change only one line: the definition of the macro
'vChainPos'.  You´re done and you don´t overlook anything.


Without macros, you have to remember what item of a stride is at which
Since the list is a local variable, the defines are placed after its
position of the stride every time you use the list.  Which macros, you
declaration.  You could place them somewhere else, maybe at the top of
don´t need to remember and simply use 'vChainPos'.
the source.  Placing them inside the scope of the list makes clear
that they are related to this particular list.


You can find an extensive example for this in the
The define "iSTRIDE_agents" is the stride of the list, 1 in this
[https://github.com/Ratany/lsl-repo git repository that accompanies this article]
case. Using "--agent;" in the loop instead of "agent -=
in projects/rfedip-reference-implementation/src/rfedipdev-enchain.lsl.
iSTRADE_agents;" would be better, but it´s done for the purpose of
demonstration.  When you have a strided list, it is a good idea to
always use a define for it: It doesn´t hurt anything and makes it easy
to modify your source because there is only one place you need to
change.  With strided lists, use defines for the indices of
stride-items so you don´t need to go through the macro definitions to
make sure the indices are ok when you change the composition of the
list.  You can look at
"projects/rfedip-reference-implementation/src/rfedipdev-enchain.lsl"
in the [https://github.com/Ratany/lsl-repo git repository that
accompanies this article] for a better example.


With macros, you can fully abstract from a particular list.  That
"kAgentKey" is a macro.  It´s a define that takes parameters.  You
makes the same code easily re-usable with different scriptsInstead
might think the parameter is an integer which is an index to the list
of
in which the UUIDs of the agents are storedIt is not:  The
parameter is ''used'' an indexThe preprocessor doesn´t know
anything about lists or integers.


There are no types involved with macro parameters as are with
variables: When you declare a function in LSL that takes parameters,
you need to declare what type the parameters are of.  Macro parameters
don´t have a type.  The preprocessor replaces stuff literally.  You
could use a vector or some random string as a parameter with
"kAgentKey".  Cpp would do the replacement just fine.  It replaces
text like a search-and-replace operation you perform with your
favourite editor does.


<cpp>
Unfortunately, you cannot define defines or macros with defines:
#define iChainLinkNo(_n)          llList2Integer(lChains, (_n))
"#define NEWLIST(name, stride) #define list name; #define
#define vChainPos(_chain)          (llList2Vector(GLPP(iChainLinkNo(_chain), [PRIM_POSITION]), 0))
iSTRIDE_##name stride" will cause cpp to stop proprocessing and to
</cpp>
print an error message.  Maybe I´ll write a perl script to
pre-preprocess my sources ... that might be easier than learning
[http://www.gnu.org/software/m4/ | m4].


"vAgentPos" and "fAgentDistance" are also macros and work the same way
as "kAgentKey".  The code to figure out which agent is the one
furthest away from the object is simple, and I´ll skip over it.  It
gets interesting when the movement is implemented, so here is
example-1_D, the complete source:


you can use


<lsl>
#include <lslstddef.h>


<cpp>
#define _LIST                      lChains
#define iChainLinkNo(_n)          llList2Integer(_LIST, (_n))
#define vChainPos(_chain)          (llList2Vector(GLPP(iChainLinkNo(_chain), [PRIM_POSITION]), 0))
</cpp>


#define fUNDETERMINED              -1.0
#define fIsUndetermined(_f)        (fUNDETERMINED == _f)


When you happen to write another script that deals with a similar or
#define fMetersPerSecond          3.0
identical list (because it does similar things), but you want to use a
#define fSecondsForMeters(d)      FMax((d), 0.03)
list that has a different name than 'lChains', there is only one
single line you need to change.


Same is with the stride-indices.  One script may require a different
#define fRANGE(_f)                (_f)
stride because it holds more elements, like the prim name in addition
#define vMINRANGE(_f)              (<_f, _f, _f>)
to the link number.  For the next script, the prim name is
#define fRandom(_fr)              (llFrand(fRANGE(_fr)) - llFrand(fRANGE(_fr)))
irrelevant.  You can define stride-indices to make that easier:
#define vRandomDistance(_fr, _fm)  (<fRandom(_fr), fRandom(_fr), fRandom(_fr)> + vMINRANGE(_fm))


#define fDistRange                3.5
#define fMinDist                  0.5


<cpp>
#define kChainKey(_n)              llList2Key(lChains, (_n) + 1)
</cpp>


default
{
event touch_start(int t)
{
unless(llDetectedKey(0) == llGetOwner())
{
return;
}


<cpp>
vector here = llGetPos();
#define iSTRIDEIDX_key            1
list agents = llGetAgentList(AGENT_LIST_PARCEL, []);
#define kChainKey(_n)             llList2Key(lChains, (_n) + iSTRIDEIDX_key)
</cpp>


#define iSTRIDE_agents    1
#define kAgentKey(x)      llList2Key(agents, x)
#define vAgentPos(x)      RemotePos(kAgentKey(x))
#define fAgentDistance(x)  llVecDist(here, vAgentPos(x))


Add another item to the stride, and all you need to do to adjust is to
float distance = fUNDETERMINED;
change a single line, or maybe a few, depending on how many items you
int goto;
have per stride.
int agent = Len(agents);
while(agent)
{
agent -= iSTRIDE_agents;


Carelessly using macros is a bad idea.  Please behold line 45 of the
float this_agent_distance = fAgentDistance(agent);
above example.  It turns into
if(distance < this_agent_distance)
{
distance = this_agent_distance;
goto = agent;
}
}


unless(fIsUndetermined(distance))
{
distance /= fMetersPerSecond;
vector offset = vRandomDistance(fDistRange, fMinDist);
offset += PosOffset(here, vAgentPos(goto));
llSetKeyframedMotion([offset,
      fSecondsForMeters(distance)],
    [KFM_DATA, KFM_TRANSLATION]);
}


<lsl>
#undef AgentKey
llSetKeyframedMotion
#undef AgentPos
([( (llList2Vector(llGetObjectDetails(llList2Key(agents, goto), [OBJECT_POS] ), 0)
#undef AgentDistance
    + ( < (llFrand(3.5) - llFrand(2.5)), (llFrand(3.5) - llFrand(2.5)), (llFrand(3.5) - llFrand(2.5)) > ))
    - (here) ),
  ( ( (llFabs( ((distance) / 3.0) >= (0.03) ) ) * ((distance) / 3.0) )
    + ( (llFabs( ((distance) / 3.0) < (0.03) ) ) * (0.03) ) )],
[KFM_DATA, KFM_TRANSLATION]);
</lsl>


}


... which is perfectly fine.  It is fine because it was considered
event state_entry()
what the 'FMax' macro does, and it is used with:
{
SLPPF(LINK_THIS, [PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_CONVEX]);
}
}
</lsl>




<cpp>
There isn´t much new to it.  "unless" is defined in "lslstddef.h" and
#define fSecondsForMeters(d)       FMax((d) / fMetersPerSecond, 0.03)
speaks for itself.  "fUNDETERMINED" and "fIsUndetermined" speak for
</cpp>
themselves, too:  There are no negative distances between positions,
and what "llVecDist()" returns is never negative.  There may be no
agents on the parcel, in which case the list of agents would be
empty.  It´s length would be 0 and the code in the while loop would
not be executed.  The variable "distance" would still have the value
it was initialized with when it was declared. What the furthest agent
is would be undetermined, and that´s exactly what the code tells you.


The movement of the object should look nice.  It looks nice when the
object always moves at the same speed.  "llSetKeyframedMotion()"
doesn´t use speed. You have to specify the duration of the
movement. Since the distance between the object and agents it moves to
varies, the duration of the movement is adjusted according to the
distance. The minimum duration "llSetKeyframedMotion()" works with is
1/45 second.  "fSecondsForMeters" makes sure that the duration is at
least 1/45 second through the "FMax" macro which is defined in
"lslstddef.h": "FMax" returns the greater value of the two parameters
it is supplied with.


When you read up about
"vRandomDistance" is used to help with that the object doesn´t hit the
[[LlSetKeyframedMotion|llSetKeyframedMotion()]], you may notice that
agent it moves to and ends up at some random distance from them, with
the page says "llSetKeyframedMotion is implemented in terms of frames
a minimum offset. "PosOffset" from "lslstddef.h" works with two
and not real time.". If you have used the function, you may have
vectors and gives the offset between them as a vector.  It´s one of my
found out that there is a limit of how short the time you specify for
favourite macros because I don´t need to ask myself anymore what
how long the motion should take can be.  The minimum time is, IIRC,
"offset" is supposed to mean: "here - there" or "there - here".  The
one frame, i. e. 1/45 second.
offset is simply "PosOffset(here, there)" when it´s an offset from
here to there and "PosOffset(there, here)" when it´s offset from there
to here.  "llSetKeyframedMotion()" uses offsets ...


It is quite natural, especially when you are used to programming in C,
The important thing here is this:  Why divide the distance and
to write:
introduce another vector for the offset when you could simply do it in
one line like this:




<cpp>
<lsl>
#define fSecondsForMeters(d)       FMax((d) / fMetersPerSecond, 1.0/45.0)
llSetKeyframedMotion([PosOffset(here, vAgentPos(goto) + vMINRANGE(0.5)),
</cpp>
      FMax(distance / fMetersPerSecond, 1.0/45.0)],
    [KFM_DATA, KFM_TRANSLATION]);
</lsl>




You can do that in C because the C compiler will replace 1/45 with the
That´s one thing all these macros are for, aren´t they?  Yes they are
result of itThe preprocessor doesn´t do that.  I don´t know what
--- and you need to be aware of what they turn into.  Cpp is designed
the LSL compiler does.
to preprocess code for a C or C++ compiler, and those are probalby a
lot smarter than an LSL compiler.  The C compiler optimizes "1.0/45.0"
by replacing it with the result of the divisionC has a ternary
operator which makes life a lot easier not only for writing macros
like "FMax": "#define MAX(a,b) (((a) > (b)) ? (a) : (b))" works for
both integers and floats --- compared to
"#define FMax(x, y) (((llFabs((x) >= (y))) * (x)) + ((llFabs((x) <
(y))) * (y)))" and another one for integers.


Now see what line 45 turns into when the source is run through cpp:
The line from above turns into:




<lsl>
<lsl>
llSetKeyframedMotion
llSetKeyframedMotion([( (llList2Vector(llGetObjectDetails(llList2Key(agents, goto), [OBJECT_POS] ),
([( (llList2Vector(llGetObjectDetails(llList2Key(agents, goto), [OBJECT_POS] ), 0)
                                      0) + (<0.5, 0.5, 0.5>)) - (here) ),
    + ( < (llFrand(3.5) - llFrand(2.5)), (llFrand(3.5) - llFrand(2.5)), (llFrand(3.5) - llFrand(2.5)) > ))
                      ( ( (llFabs( (distance / 3.0) >= (1.0 / 45.0) ) ) * (distance / 3.0) ) + ( (llFabs( (
    - (here) ),
                                  distance / 3.0) < (1.0 / 45.0) ) ) * (1.0 / 45.0) ) )],
  ( ( (llFabs( ((distance) / 3.0) >= (1.0 / 45.0) ) ) * ((distance) / 3.0) )
                    [KFM_DATA, KFM_TRANSLATION]);
    + ( (llFabs( ((distance) / 3.0) < (1.0 / 45.0) ) ) * (1.0 / 45.0) ) )],
[KFM_DATA, KFM_TRANSLATION]);
</lsl>
</lsl>




Notice that 1/45 needs to be computed --- unless the LSL compiler
Letting aside that the code would be totally different when written in
optimizes it --- three times.
C, a C compiler would optimze that.  I don´t know what the LSL
compiler does.


Things like that can easily go unnoticed. The variable 'distance' is
'''Always be aware of what your macros turn into.''' The code from
divided by 3 three times. It would be better to modify the
example-1_D turns into:
source to avoid this:




<lsl>
<lsl>
[...]
distance /= 3.0;
#define fSecondsForMeters(d)       FMax((d), 0.03)
vector offset = ( < (llFrand((3.5)) - llFrand((3.5))), (llFrand((3.5)) - llFrand((3.5))),
[...]
                  (llFrand((3.5)) - llFrand((3.5))) > + (<0.5, 0.5, 0.5>));
unless(fIsUndetermined(distance))
offset += ( (llList2Vector(llGetObjectDetails(llList2Key(agents, goto), [OBJECT_POS] ),
{
                          0)) - (here) );
distance /= fMetersPerSecond;
llSetKeyframedMotion([offset, ( ( (llFabs( ((distance)) >= (0.03) ) ) * ((distance)) ) + ( (
llSetKeyframedMotion([PosOffset(here, vAgentPos(goto) + vRandomDistance), fSecondsForMeters(distance)], [KFM_DATA, KFM_TRANSLATION]);
                                    llFabs( ((distance)) < (0.03) ) ) * (0.03) ) )], [KFM_DATA, KFM_TRANSLATION]);
}
[...]
</lsl>
</lsl>




When you need the resulting LSL script more readable --- which is
It´s still awful to read.  What did you expect?  The source looks nice
something I don´t care about much --- your souce would look like this:
and clear because macros and defines are used, wich is one of the
reasons to use them.  More importantly, this code doesn´t employ
numerous obsolete divisions because I checked the outcome and modifed
my source.
 
'''Always check the script for what your macros turn into.'''  It´s
easy to check, just load the debugging version into your favourite
editor.  You can even use the built-in editor:  change the "o" for an
"i" and click on the "Edit" button as described earlier.
 
One thing is new in example-1_D:  The defines that were used to make
the list with the UUIDs of the agents usable are undefined with
"#undef".  It´s not necessary to do this, it´s tidy.  As mentioned
before, the list has local scope.  That´s the reason I undefined the
defines that relate to it.  It ends their "scope" as well.  You can
create new defines with the same names outside this scope if you
like. They are undefined and out of the way.  Perhaps at some time,
you include something into your source that happens to use defines
with the same name.  You would get an error message and have to go
back to the perhaps old source and figure out where you can undefine
them.  It doesn´t hurt to undefine them right away and saves you time
later.
 
======Script memory and performance with cpp======
 
You may have seen or written scripts that use variables as constants.
Look at this example:




Line 1,064: Line 1,098:




#define fUNDETERMINED              -1.0
#define WITH_DEFINE_FLOAT 0
#define WITH_DEFINE_INT  0
 


#define fIsUndetermined(_f)        (fUNDETERMINED == _f)
#if WITH_DEFINE_FLOAT
#define ftest 1.0
#else
float ftest = 1.0;
#endif


#define fMetersPerSecond          3.0
#if WITH_DEFINE_INT
#define fSecondsForMeters(d)      FMax((d), 0.03)
#define itest 1
#else
int itest = 1;
#endif


#define fRandom                    (llFrand(3.5) - llFrand(2.5))
#define vRandomDistance            (<fRandom, fRandom, fRandom>)




Line 1,079: Line 1,120:
event touch_start(int t)
event touch_start(int t)
{
{
if(llDetectedKey(0) == llGetOwner())
apf("---");
{
apf("the float is", ftest, "and the int is", itest, " with ", llGetFreeMemory(), " bytes free");
vector here = llGetPos();
apf("the float is", ftest, "and the int is", itest, " with ", llGetFreeMemory(), " bytes free");
apf("the float is", ftest, "and the int is", itest, " with ", llGetFreeMemory(), " bytes free");
apf("the float is", ftest, "and the int is", itest, " with ", llGetFreeMemory(), " bytes free");
apf("the float is", ftest, "and the int is", itest, " with ", llGetFreeMemory(), " bytes free");
apf("the float is", ftest, "and the int is", itest, " with ", llGetFreeMemory(), " bytes free");
apf("the float is", ftest, "and the int is", itest, " with ", llGetFreeMemory(), " bytes free");
apf("the float is", ftest, "and the int is", itest, " with ", llGetFreeMemory(), " bytes free");
apf("the float is", ftest, "and the int is", itest, " with ", llGetFreeMemory(), " bytes free");
apf("the float is", ftest, "and the int is", itest, " with ", llGetFreeMemory(), " bytes free");
apf("---");
}
}
</lsl>
 
 
You can find this example in the
[https://github.com/Ratany/lsl-repo git repository that accompanies this article]
as "projects/memtest/src/memtest.lsl".
 
It shows that the script needs 8 bytes more memory when you use either
a float or an integer variable --- or both! --- instead of defines.
That means when you declare a variable, declaring another one doesn´t
make a difference, provided that the variables are of the types float
and integer.
 
I give up artifical testing at this point.  Either the test or the
results must be flawed.  One thing I wanted to know is whether it
makes a difference if you have several lines with a number (like 1 or
1.0) vs. the same several lines with a variable instead of the
number.  This test doesn´t show that, perhaps due to optimizations
the LSL compiler performs.
 
I use defines for constants --- not because it apparently may save 8
bytes, but because it makes sense.  That you can switch between
defines and variables for constants makes testing with "real" scripts
easy.
 
'''Test with your actual script what difference it makes.'''
 
It seems obvious that you would waste memory when you use a macro that
the preprocessor turns into a lengthy expression which occurs in the
script multiple times.  When it matters, test it; you may be
surprised.
 
You can also try to test for performance.  Provided you find a
suitable place to set up your test objects and let the tests run for
at least a week, you may get valid results.  Such tests could be
interesting because you may want to define a function instead of a
macro --- or a function that uses a macro --- to save script memory
when the macro turns into a lengthy expression.  In theory, the macro
would be faster because using a function requires the script
interpreter to jump around in the code and to deal with putting
parameters on the stack and with removing them while the macro does
not.  But in theory, declared variables use script memory.


list agents = llGetAgentList(AGENT_LIST_PARCEL, []);
When programming in C and you have a statement like


#define kAgentKey(x)      llList2Key(agents, x)
#define vAgentPos(x)      RemotePos(kAgentKey(x))
#define fAgentDistance(x)  llVecDist(here, vAgentPos(x))


float distance = fUNDETERMINED;
<lsl>
int goto;
if((A < 6) && (B < 20))
int agent = Len(agents);
[...]
while(agent)
</lsl>
{
--agent;


float this_agent_distance = fAgentDistance(agent);
if(distance < this_agent_distance)
{
distance = this_agent_distance;
goto = agent;
}
}


unless(fIsUndetermined(distance))
the part '(B < 20)' will not be evaluated when '(A < 6)' evaluates to
{
false.  This is particularly relevant when you, for example, call a
distance /= fMetersPerSecond;
function or use a macro:
vector offset = vRandomDistance;
offset += PosOffset(here, vAgentPos(goto));
llSetKeyframedMotion([offset,
                              fSecondsForMeters(distance)],
                                                                    [KFM_DATA, KFM_TRANSLATION]);
}


#undef AgentKey
#undef AgentPos
#undef AgentDistance


}
<lsl>
}
if((A < 6) && (some_function(B) < 20))
}
[...]
</lsl>
</lsl>




The LSL script is then a bit easier to read:
LSL is stupid in that it will call the function or evaluate the
expression the macro turned into even when '(A < 6)' evaluates to
false.  It´s impossible for the whole condition to evaluate to true
when '(A < 6)' is false, and evaluating a condition should stop once
its outcome has been evaluated.  Calling a function takes time, and
the function even might be fed with illegal parameters because B could
have an illegal value the function cannot handle, depending on what
the value of A is:




<lsl>
<lsl>
default
integer some_function(integer B)
{
{
touch_start(integer t)
    llSay(0, "10 diveded by " + (string)B + " is: " + (string)(10 / B));
{
    return 10 / B;
if(llDetectedKey(0) == llGetOwner())
}
{
vector here = llGetPos();
list agents = llGetAgentList(AGENT_LIST_PARCEL, []);
float distance = -1.0;
integer goto;
integer agent = llGetListLength(agents);


while(agent)
{
--agent;
float this_agent_distance = llVecDist(here, llList2Vector(llGetObjectDetails(llList2Key(agents, agent),
                                                                      [OBJECT_POS] ), 0));


if(distance < this_agent_distance)
default
{
{
distance = this_agent_distance;
    touch_start(integer total_number)
goto = agent;
    {
}
        integer B = 20;
}
        integer A = 5;


if(!((-1.0 == distance)))
        if(A < 8) B = 0;
{
   
distance /= 3.0;
        if((A < 6) && (some_function(B) < 20))
vector offset = ( < (llFrand(3.5) - llFrand(2.5)), (llFrand(3.5) - llFrand(2.5)), (llFrand(3.5) - llFrand(2.5)) > );
        {
offset += ( (llList2Vector(llGetObjectDetails(llList2Key(agents, goto), [OBJECT_POS] ), 0)) - (here) );
            llSay(0, "stupid");
llSetKeyframedMotion([offset,
        }
      ( ( (llFabs( ((distance)) >= (0.03) ) ) * ((distance)) )
    }
+ ( (llFabs( ((distance)) < (0.03) ) ) * (0.03) ) )],
    [KFM_DATA, KFM_TRANSLATION]);
}
}
}
}
}
</lsl>
</lsl>




Note: If you want to try this script, the object you put it in must
This example will yield a math error because some_function() is called
use the so-called "prim equivalency system"Otherwise,
even when it should not be called.  Such nonsense should be
llSetKeyframedMotion() doesn´t workYou can either edit the object
fixedCalling the function is an undesireable side effect.  To work
and set the physics shape to "convex hull" or "none", or add a line to
around it so that some_function() isn´t called, you need to add
the script that sets the physics shape, like 'SLPPF(LINK_ROOT,
another "if" statement, which eats memory.
[PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_CONVEX]);'.


Depending on what the function or the macro does, your "if" statements
can affect performance.  This is independent of using a preprocessor,
but since you may be tempted to use macros in your sources when you
use a preprocessor, you may be tempted to use them in "if" statements
and create more undisirable side effects than you otherwise would.


'''So when using macros, you need to be aware of what they turn into.  Always check the outcome of the preprocessing of your source.'''
'''Avoid "if" statements which evaluate expressions that should not be evaluated.'''


Checking the outcome is the easiest and most reliable way to spot
======Use Parentheses======
undesirable side effects of macro usage.  You can improve the
efficiency, performance and memory usage of your script by rewriting
your source and sometimes by using functions instead of macros, or by
defining a function that uses a macro.  It´s sometimes so easy to
create a script that unwanted side effects go unnoticed.


One last, yet very important thing with macros, is the usage of
When you look at this simple macro
brackets.  When you look at this simple macro




Line 1,210: Line 1,272:




it will print 15.  Most of the time, your code will be more like this:
it will print 15.  Most of the time, your source will be more like this:




Line 1,230: Line 1,292:




You need to use brackets:
You need to use parentheses:




Line 1,241: Line 1,303:




You need even more brackets, because:
You need even more parentheses, because:




Line 1,258: Line 1,320:




Imagine you defined 'plus3times3' without any brackets at all.  You´d
Imagine you defined 'plus3times3' without any parentheses at all.  You´d
waste hours trying to figure out why your script yields incredibly
waste hours trying to figure out why your script yields incredibly
weird results.
weird results.


To get it right, you must use more brackets:
To get it right:




Line 1,279: Line 1,341:




There is quite a difference between 63 and 324.  Use brackets.  I also
There is quite a difference between 63 and 324.  Use parentheses.  I also
always use brackets with conditions because it makes them unambiguous:
always use parentheses with conditions because it makes them unambiguous:




Line 1,289: Line 1,351:




is ambiguous:  What is wanted here?  That A or B be true and that C or
is ambiguous. What I meant is:
D are greater than 500?  Or that A or B be true and that C be true and
that D is greater than 500?  Or that either A or (B and C) are true
or that D be greater than 500? What I meant is:




Line 1,301: Line 1,360:




That is not ambiguous.  Save yourself the hassle of ambiguity and use
That is not ambiguous.  Since "&&" usually takes precedence over "||",
bracketsMy LSL scripts tend to have tons of brackets, a lot of them
it´s different from the version without parentheses(It probably
not strictly needed because they come from macro definitionsThey
doesn´t matter in this case, but I´m not even trying to figure it
don´t hurt anything.
out. It does matter in C because it has an effect on which of the
 
expressions are evaluated and wich are not.  Simply write what you
On a side note: When programming in C and you have a statement like
mean, with parentheses, and don´t let LSL get you to develop a bad
 
style.) When you have the above "if" statements in a macro, use
 
parentheses around "A", "B", "C" and "D", for reasons shown above.
<lsl>
if((A < 6) && (B < 20))
[...]
</lsl>
 
 
the part '(B < 20)' will not be evaluated when '(A < 6)' evaluates to
false.  This is particularly relevant when you, for example, call a
function:
 


<lsl>
Save yourself the hassleMy LSL scripts tend to have lots of
if((A < 6) && (some_function(B) < 20))
parentheses, a lot of them not strictly needed because they come from
[...]
macro definitionsThey don´t hurt anythingNot having them can
</lsl>
hurt badly.
 
 
LSL is stupid in that it will call the function even when '(A < 6)'
evaluates to falseIt´s impossible for the whole condition to
evaluate to true when '(A < 6)' is false, and evaluating a condition
should stop once its outcome has been evaluated.  Calling a function
takes time, and the function even might be fed with illegal parameters
because B could have an illegal value the function cannot handle,
depending on what the value of A is:
 
 
<lsl>
integer some_function(integer B)
{
    llSay(0, "10 diveded by " + (string)B + " is: " + (string)(10 / B));
    return 10 / B;
}
 
 
default
{
    touch_start(integer total_number)
    {
        integer B = 20;
        integer A = 5;
 
        if(A < 8) B = 0;
   
        if((A < 6) && (some_function(B) < 20))
        {
            llSay(0, "stupid");
        }
    }
}
</lsl>
 
 
This example will yield a math error because some_function() is called
even when it should not be calledSuch nonsense should be fixedTo
work around it so that some_function() isn´t called, you need to add
another if() statement, which eats memory.


=====How to use cpp=====
=====How to use cpp=====
Line 1,813: Line 1,821:




[[User:Ratany Resident|Ratany Resident]] 13:03, 8 January 2014 (PST)
[[User:Ratany Resident|Ratany Resident]] 15:12, 9 January 2014 (PST)
{{LSLC|Tutorials}}
{{LSLC|Tutorials}}

Revision as of 16:12, 9 January 2014


This article is currently being worked on.


How to make writing LSL scripts easier

This article is currently being worked on.


About

This article is about tools you can use to make creating LSL scripts easier. It tries to explain how to combine and use them for this very purpose.

The idea of using cpp comes from the source of the Open Stargate Network stargates. It´s not my idea, I´m merely using it.

I want to thank the developers of these stargates. Not only their stargates are awesome. The idea of using cpp for LSL scripts is also awesome. It has made it so much easier to create LSL scripts that I have made many I otherwise would never have made.

Editing this Article

This article is written in my favourite editor. When I update the article on the wiki, I edit the whole article, delete everything and paste the new version in. That´s simply the easiest and most efficient way for me.

Unfortunately, this means that your modifications may be lost when I update the article and don´t see them.

Please use the "discussion" page of this article to post contributions or to suggest changes so they can be added to the article.

Tools

This section tries to give an overview of some tools you can use. It does not tell you how to install them.

It´s your computer you´re reading this article with, and you know better how to install software on your computer than I do. How you install software also depends on what operating system you are using:

I´m using Linux, which is freely available in many different distributions. I´m using Fedora, so to install Emacs, I run 'yum install emacs'. If you don´t use Fedora, telling you this probably doesn´t help you. Or you switch to Fedora; it works remarkably well.

Editor

To create scripts, you do need an editor. All sl clients I´ve seen have one built in, and it´s the worst editor I´ve ever seen. Fortunately, you don´t need to use it.

You can use any editor you like. You probably want an editor that at least gets the indentations right and has useful functions like 'indent-region', 'sort-lines', 'goto-matching-fence', 'whitespace-cleanup' etc., lets you define macros and write your own functions, highlights matching brackets, supports code-folding and tags, does customizable syntax highlighting, lets you edit several files at once, restores them when you restart the editor and has something like 'auto-revert-mode' so you can read the debugging messages of your scipts which they write into your chat.txt --- plus lots of other things like regexp search/replace that don´t come to mind at the moment.

The built-in script editor is useful for replacing the scripts you have been working on in your favourite editor and to look at the error messages that may appear when your script doesn´t compile. Don´t use it for anything else.

Not using the buit-in script editor has several advantages like:


  • You can use a decent editor!
  • You have all your sources safely on your own storage system.
  • Your work does not get lost or becomes troublesome to recover when your sl client crashes, when you suddenly get logged out, when "the server experiences difficulties" again, when the object the script is in is being returned to you or otherwise gets "out of range".
  • You don´t need to be logged in to work on your sources.
  • You can preprocess and postprocess and do whatever else you like with your sources before uploading a script.
  • You can share your sources and work together with others because you can use tools like git and websites like github. (Even when you don´t share your sources, git is a very useful tool.)
  • How fast you can move the cursor in the editor does not depend on the FPS-rate of your sl client.
  • You can use fonts easier to read for you, simply change the background and foreground colours and switch to the buffer in which you read your emails or write a wiki article like this one ...
  • Though it doesn´t crash, a decent editor has an autosave function and allows you to restore files in the unlikely case that this is needed.
  • And yes, you can use a decent editor :)
Emacs

Emacs is my favourite editor. It does not only get the indentations right and does all the other things mentioned above. It offers much more and has been around since a while. If you had learned to use it twenty-five years ago, that would have saved you the time wasted with other editors which nowadays may not even exist anymore.

Syntax Highlighting

Emacs comes with syntax highlighting for many programming languages. It doesn´t come with syntax highlighting for LSL, but LSL syntax highlighting modes for emacs are available.

You can use Emacs_LSL_Mode.

A mofied version of lsl-mode is in the git repository that accompanies this article as "emacs/lsl-mode.el". This version has been modified for use with a file I named "lslstddef.h" ("projects/include/lslstddef.h" in the repository). Following sections will refer to and use this file.

Tags

Emacs supports so-called tags. Using tags allows you to find the declaration of functions, defines, macros and variables you use in your sources without searching. It doesn´t matter in which file they are. This will be described in following sections.

Git

Emacs can support git (and other CVS systems). You may need to install additional packages for it. I haven´t tried out any of them yet.

Other Editors

In case you find that you don´t get along with emacs, you may want to check out this page about editors. Try vi, or vim, instead.

You don´t need to try any other editors. EPM was nice, but it crashed all the time.

In any case, use an editor you are happy with because it does what you want. An editor is one of the most essential tools. You want your tools to work the way you want them to. You don´t want tools that get into your way.

Automatically replacing Scripts

The editor built into sl clients comes with buttons like to save and to reset the script which is being edited. There is also a button labled "Edit". As the label of this button suggests, the button is for editing the script. The built-in editor is not for editing the script, though it´s not impossible to kinda use it for that.

Built-in Editor with "Edit" button

Open a script in your sl client and click on the edit button of the built-in editor. You might get an error message telling you that the debug setting "ExternalEditor" is not set. Don´t panic! Set this debug setting so that your favourite editor is used, and click on the edit button again. This is merely to show the concept, and things will get more convenient.

Debug Setting "ExternalEditor"

Your favourite editor should now start and let you edit the script. This works because your sl client saves the script which you see in the built-in editor to a file on your storage system. Then your sl client starts whatever you specified in the debug setting as "external editor". To tell your favourite editor which file the script has been saved to, the place holder %s is used with the debug setting "ExternalEditor". The place holder is replaced with the name of the file your sl client has saved.

Once the script has been saved to a file, your sl client monitors this file for changes. When the file changes --- which it does when you edit and then save it with your favourite editor --- your sl client replaces the script in the built-in editor with the contents of the file.

This is nice because you could write a new script while you are offline. When you are ready to test the script, you could log in with your sl client, build a box, create a new script in the box, open the script, click on the "Edit" button, delete the contents of the new script in your favourite editor, insert the script you have written and save it.

Your sl client would notice that the file changed and put it into the built-in editor. It would let the sl server compile the script and show you the error messages, if any. When your script compiles and works right away, you´re done --- if not, you can use the built-in editor to check the error messages and your favourite editor to change the source as needed. Then you need to replace the script again.

At this point, things suddenly aren´t nice anymore: Replacing the script with your favourite editor all the time is a stupid process (unless you program your editor to do it for you). Even when you have only one script, having to replace the script over and over again while you are adding to it and debugging it sucks. It sucks even more when your object has 11 scripts working toegether and a little change makes it necessary to replace all of them.

You could mess around with the built-in editor and try to edit your script further with it, saving the replacing. That also sucks: The built-in editor is unusable, and you end up with at least two versions of the script. Since you want the script safely stored on your storage system and not lose your work when your sl client crashes or the like, you end up replacing the script on your storage system with the version you have in the built-in editor every now and then. When your object has 11 scripts that work together, it eventually becomes a little difficult to keep an overview of what´s going on --- not to mention that you need a huge monitor with a really high resolution to fit 11 built-in editor windows in a usable way on your screen to work on your scripts since there is no C-x b with the built-in editor to switch to a different buffer ...

Fortunately, there is a much better and very simple solution: A program which is started by your sl client instead of your favourite editor can replace the script for you. It´s the same concept from above you have tried out now. The concept does not require that you must abuse your favourite editor to replace scripts.

You can easily write a program that replaces files yourself. I wrote one when I got tired of all the replacing. It´s not really a program, but a perl script. It´s written in perl because perl is particularly well suited for the purpose. It looks like this:


<perl>

  1. !/bin/perl


  1. This program is free software: you can redistribute it and/or
  2. modify it under the terms of the GNU General Public License as
  3. published by the Free Software Foundation, either version 3 of the
  4. License, or (at your option) any later version.
  5. This program is distributed in the hope that it will be useful, but
  6. WITHOUT ANY WARRANTY; without even the implied warranty of
  7. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  8. General Public License for more details.
  9. You should have received a copy of the GNU General Public License
  10. along with this program. If not, see
  11. <http://www.gnu.org/licenses/>.


  1. usage: replace.pl <filename>
  2. replace file A with file B when the first line of file A is a key
  3. found in a look-up table; file B is specified by the value of the
  4. key
  5. Entries in the look-up table are lines. Each line holds a key-value
  6. pair, seperated by a colon and a space: 'key: value'.


use strict; use warnings; use autodie;

use File::Copy;


  1. the editor to use
  2. Note: Use an editor that waits before it exits until you are
  3. finished editing the file. Your sl client stops monitoring the file
  4. when the editor exits (or forks off into the background) before
  5. you´re done editing, and it may not replace the contents of its
  6. built-in editor with the contents of the file you´re editing.

my $editor = "emacsclient";

  1. "-c" makes emacsclient create a new frame. If you start your
  2. favourite editor without such a parameter, you want to remove
  3. $editorparam here an in the 'start_editor' function.

my $editorparam = "-c";


  1. a wrapper function to start the editor

sub start_editor

 {
   my (@files_to_edit) = @_;
   system($editor, $editorparam, @files_to_edit);
 }


  1. unless the filename given as parameter is *.lsl, edit the file

unless($ARGV[0] =~ m/.*\.lsl/) {

 start_editor($ARGV[0]);
 exit(0);

}


  1. the file name of the lookup table; specify an absolute path here

my $table = "/absolute/path/to/replaceassignments.txt";


  1. read the first line of the file; unless it matches a pattern like
  2. "// =filename.[o|i]", edit the file and the lookup table

open my $script, "<", $ARGV[0]; my $line = <$script>; close $script; chomp $line; $line =~ s!// =!!; unless(($line =~ m/.*\.o/) || ($line =~ m/.*\.i/))

 {
   start_editor($ARGV[0], $table);
   exit(0);
 }


  1. look up the key in the lookup table
  2. the key is looked up for *.o and *.i so that only one entry per
  3. script is needed in the lookup table to cover both *.i and *.o,
  4. provided that the directory structure doesn´t change

my $i_line = $line; $i_line =~ s/\.i$/\.o/; $i_line .= ": "; $line .= ": "; my $replacementfile = undef; open my $assign, "<", $table; while( <$assign> )

 {
   # ignore lines starting with "//" as comments
   #
   unless( m!^//!)
     {

chomp $_; if( m/$line/ || m/$i_line/) { $replacementfile = $'; last; }

     }
 }

close $assign;

if(defined($replacementfile))

 {
   if($line =~ m/.*\.i:/)
     {

$replacementfile =~ s!/bin/!/dbg/!; $replacementfile =~ s/\.o$/\.i/;

     }
   # when the value of the key looks ok, replace the file, otherwise edit
   # the file and the table
   #
   if(($replacementfile =~ m/.*\.o$/) || ($replacementfile =~ m/.*\.i$/))
     {

copy($replacementfile, $ARGV[0]);

     }
 }

else

 {
   start_editor($ARGV[0], $table);
 }

</perl>


You´ll find this script in the git repository that accompanies this article as "projects/bin/replace.pl". What it basically does is simple:

  • look at the name of the file which is supplied as a command line argument; when it´s not named like "*.lsl", start your favourite editor to edit the file
  • otherwise, look at the first line of the file
  • when the line starts with "// =", treat the rest of the line from there as a key; otherwise start your favourite editor to edit the file and another one which is a lookup-table
  • look up the key in a look-up table (a file) that associates the key with a value
  • when the key is found in the table, replace the file saved by the sl client with the file the name of which is specified by the value associated with the key
  • when the key is not found in the table, start your favourite editor to edit the file that was saved by the sl client and to edit the table


Entries in the table are so-called key-value pairs. The table looks like this:


<lsl> example-script.o: /path/to/bin/example-script.o

// // this is a comment //

foobar-script.o: /path/to/bin/foobar-script.o </lsl>


Note: The space between the colon and the file name is mandatory (because the perl script is written like that).

This means when you have a script in the built-in script editor like


<lsl> // =example-script.o

default { state_entry() { Say(0, "test"); } } </lsl>


and click on the "Edit" button of the built-in script editor, the script will automatically be replaced with the contents of the file "/path/to/example-script.o".

To make this work, you need to:


  • modify the perl script to start your favourite editor (in the unlikely case that your favourite editor isn´t emacs)
  • modify the perl script to tell it where to find the table that associates the keys and values ('my $table = "/absolute/path/to/replaceassignments.txt";')
  • change the debug setting "ExternalEditor" so that your sl client starts above perl script instead of your favourite editor
  • create the look-up table (the table can be empty)
  • make sure that your script starts with a line like "// =example-script.o"
  • try out the perl script:


You should first try out the perl script to see if it works.

When you have your sl client start it, it is difficult or impossible to see error messages you may get when running the perl script:


<lsl> [~/tmp] echo "// =example-script.o" > example-script.lsl [~/tmp] ~/src/lsl-repo/projects/bin/replace.pl example-script.lsl Can't open '/absolute/path/to/replaceassignments.txt' for reading: 'No such file or directory' at ~/src/lsl-repo/projects/bin/replace.pl line 97 [~/tmp] </lsl>


You see what I mean: You forgot to adjust the file name of the look-up table (or forgot to create the table) and get an error message because the file doesn´t exist. If you had your sl client start the perl script, apparently nothing would happen when you click the "Edit" button.

Once you have verified that the perl script works, you´re fine. However, when you have a typing error in the lookup-table, the script in the built-in editor might silently not be replaced with the new version. (You could modify the perl script so that it beeps when it does not replace the script, or experiment with something like 'xterm -hold -e replace.pl "%s"'. If 'xterm -hold ...' works, you could do something to run 'make' (see below) automatically when you click the "Edit" button.)

The perl script starts your favourite editor when it doesn´t find the key (i. e. "example-script.o") in the table. You can then edit both the script and the table. This means that you don´t need to fill the table in advance. You can start with an empty table ('touch /absolute/path/to/replaceassignments.txt') and add entries as you go.

The perl script uses the first entry in the table that matches. If you plan to have multiple scripts with the same name, you can make adjustments to the Makefile (see below) and don´t need to modify the perl script. It´s a good idea to decide about this before filling your table: When you want multiple scripts with the same name some time later, you will have to edit your table.

The build system (see below) creates "compressed" versions of the scripts and versions that are not compressed. The "compressed" versions are named *.o and the not-compressed versions are named *.i.

The "compressed" versions can be pretty unreadable, which can make debugging difficult. The not-compressed version is formattted with astyle (see below) to make it easier to read. It can be used for debugging. (The "compressed" version is what you want to put as a script into your object.)

To see the debugging version of the script in the built-in editor, change the first line of the script in the built-in editor from like "// =example-script.o" to "// =example-script.i" and click on the "Edit" button. That puts the version of the script which is not compressed into the built-in editor. Switch the "i" back to an "o", click the "Edit" button and the "compressed" version is put back.

Above perl script does the replacing for you, provided that the debugging version is in the "dbg" directory and the "compressed" version is in the "bin" directory and that both "dbg" and "bin" are in the same directory.

When you use the build system, you do not need to add entries to the lookup-table. They are automatically added as needed when you run 'make' (see below).

A Preprocessor

The Wikipedia article about preprocessors gives an interesting explanation of what a preprocessor is and provides an overview. For the purpose of making it easier to create LSL scripts, a preprocessor as is used with C (and C++) compilers is a very useful tool.

The particular preprocessor I´m using is called "cpp" and comes with GCC.

What a preprocessor does

A preprocessor enables you:


  • to include files into others
  • to define so-called "defines" and so-called "macros"
  • to create different scripts from the same sources through the use of conditionals


That doesn´t sound like much, yet these are rather powerful features. Some of the advantages that come with these features are:


  • You can create libraries and include them into your sources as needed: Your code becomes easily re-usable.
  • Your sources become much easier to modify and to maintain because instead of having to change parts of the code in many places, you can change a definition at one single place --- or code in a library or a header file --- and the change will apply everywhere.
  • Scripts may become less prone to bugs and errors because code is easy to re-use and easier to modify and to maintain. That can save you a lot of time.
  • You may create scripts that use less memory because instead of using variables as constants, you can use so-called defines. This may allow you to do something in one script rather than in two and save you all the overhead and complexity of script-intercommunication.
  • Macros can be used instead of functions, which may save memory and can help to break down otherwise unwieldy logic into small, simple pieces which are automatically put together by the preprocessor.
  • Using macros can make the code much more readable, especially with LSL since LSL is often unwieldy in that it uses lengthy function names (like llSetLinkPrimitiveParamsFast(), ugh ...) and in that the language is very primitive in that it doesn´t even support arrays, let alone user-definable data structures or pointers or references. It is a scripting language and not a programming language.
  • Using macros can make it much easier to create a script because you need to define a macro only once, which means you never again need to figure out how to do what the macro does and just use the macro. The same goes for libraries.
  • Frequently used macros can be gathered in a file like "lslstddef.h" which can be included into all your scripts. When you fix a bug in your "lslstddef.h" (or however you call it), the bug is fixed in all your scripts that include it with no need to look through many scripts to fix the very same bug in each of them at many places in their code. The same goes for bugs fixed in a library.
  • Macros can be used for frequently written code snippets --- like to iterate over the elements of a list --- and generate several lines of code automatically from a single line in your source. That can save you a lot of typing.
  • Conditionals can be used to put code which you have in your source into the resulting LSL script depending on a single setting or on several settings, like debugging messages that can be turned on or off by changing a single digit.
  • Using conditionals makes it simple to create libraries and to use only those functions from libraries which are needed by a particular script.
  • Macros and defines in your sources only make it into a script when they are used.


There are probably lots of advantages that don´t come to mind at the moment. You can be creative and find uses and advantages I would never think of.

How cpp works

Some extensive documentation about cpp can be found here.

Imagine you want to create a script that figures out which agent on the same parcel is the furthest away from the object the script is in and moves the object towards this agent when the owner of the object touches it.

In the following, the source for the script will be developed piece by piece. You can find the complete source in the git repository that accompanies this article in "projects/example-1/src/example-1.lsl".

Obviously, the script will need a touch event, a way to find out which agent is the furthest away and a a way of moving to the agent.

In this case, keyframed motion (KFM) is a good way to get the object moving: It doesn´t require the object to be physical and thus lets it move through obstacles like walls. KFM requires the object to use the so-called "prim equivalency system", which means that it must either be mesh, or the physics type of one of its prims must be either "convex hull" or "none". This can either be set in the editing window when editing the object, or by the script. The physics type of the root prim cannot be "none", and since it´s hard to say whether "none" or "convex hull" is better for a particular object, the script will simply use "convex hull". Since the physics type of the object needs to be set only once, the script should use a state_entry event to set it. It can be set with llSetLinkPrimitiveParamsFast().

Finding out which agent is the furthest away from the object requires to find out what agents there are. A sensor is unsuited because the agent could be out of sensor range, and the sensor would report no more than the closest sixteen agents. Using llGetAgentList() works much better for this. It is also necessary to find out what the distances between the agents and the object are, for which llGetObjectDetails() can be used.

The object should move to a position at "some distance" from the agent. It seems nicer to use a random distance rather than a fixed one. This can easily be done by creating a "random vector", i. e. a vector the components of which are randomized. What |llFrand() returns is not really a random number, but it´s sufficient for this purpose.

As with all sources, I want to use some of the defines that are gathered in my "lslstddef.h". One of them is the macro "RemotePos()". It is a shortcut for using llGetObjectDetails() to find out the position of and object or prim or agent. Another one is "SLPPF()", a shortcut for llSetLinkPrimitiveParamsFast() because llSetLinkPrimitiveParamsFast() is awfully long.

This makes for a source (example-1_A) that looks like this:


<lsl>

  1. include <lslstddef.h>


default { event touch_start(int t) { // find furthest agent and move to them on touch by owner }

event state_entry() { // set the physics type } } </lsl>


The '#include' in the first line is a so-called preprocessor directive. It tells the preprocessor to do something. There are other directives, like "#define", "#undef", "#if", "#endif" and some others.

"#include" tells cpp to find a file "lslstddef.h" within some directories it searches through by default and to make it so as if the contents of this file were part of the source. Suppose you have the source saved as "example-1.lsl": The preprocessor includes "lslstddef.h" into "example-1.lsl". It´s output will not contain the first line because the first line is a preprocessor directive.

The source ("example-1.lsl") is never modified by the preprocessor. Including "lslstddef.h" makes everything in "lslstddef.h" known to the preprocessor. The preprocessor produces an output which is a copy of the source with modifications applied to it as the directives, like the ones in "lslstddef.h", demand.

"lslstddef.h" has a lot of directives for the preprocessor. Most of them define something with "#define". When something is defined with "#define", it is a so-called define. Macros can also be defined with "#define". Macros are called macros because they are defines that take parameters.


You may have noticed that in example-1_A, it doesn´t read "touch_start()" but "event touch_start()". That´s because I write my sources like that: Putting "event" before events makes them easier to find and the source looks nicer and is easier to read for me. In your sources, you can do it the same way or use something else than "event" or just write "touch_start()" instead. You can see what it looks like on my screen in the example picture --- I currently use green on black in emacs. It sticks out, and "event" makes a good search string that saves me searching for particular events.

Easier to read

The LSL compiler wouldn´t compile the source, not only because it doesn´t know what to do with "#include" directives, but also because it doesn´t compile statements like "event touch_start()". Therefore, "lslstddef.h" defines that "event" is nothing:


<cpp>

  1. define event

</cpp>


By including "lslstddef.h", cpp knows this and replaces all occurances of "event" with nothing, making "event" disappear from the result. This doesn´t mean that "event" is not defined. It´s a define. You can use the directive "#ifdef" with such defines:


<cpp>

  1. ifdef event
  2. define test 1
  3. endif

</cpp>


This would create a define named "test" only if "event" is defined. Much like cpp replaces "event" with nothing, it would now replace "test" with "1" because "test" is defined to be "1".


The next thing the example script needs is to set the physics type. The define "SLPPF" is a shortcut for "llSetLinkPrimitiveParamsFast", so use that. The script also needs to check if it was the owner of the object who touched it and do nothing when it was someone else. It needs to know the position of the object the script is in and what agents are around so it can figure out which agent is the furthest away from the object. Add all this to the source, which is now example-1_B:


<lsl>

  1. include <lslstddef.h>


default { event touch_start(int t) { unless(llDetectedKey(0) == llGetOwner()) { return; }

vector here = llGetPos(); list agents = llGetAgentList(AGENT_LIST_PARCEL, []); }

event state_entry() { SLPPF(LINK_THIS, [PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_CONVEX]); } } </lsl>


With


<cpp>

  1. define event
  2. define SLPPF llSetLinkPrimitiveParamsFast

</cpp>


in "lslstddef.h", the preprocessor replaces "event" with nothing and "SLPPF" with "llSetLinkPrimitiveParamsFast".


Lists in LSL can be a pita to work with. You end up using functions to pick out list elements like llList2Key(). They combine with functions that return lists and lead to unwieldy expressions like


<lsl> llList2Vector(llGetObjectDetails(llList2Key(agents, 0), [OBJECT_POS]), 0); </lsl>


which combine with other functions like llVecDist() into even longer expressions like


<lsl> llVecDist(llList2Vector(llGetObjectDetails(llList2Key(agents, 0), [OBJECT_POS] ), 0),

         llList2Vector(llGetObjectDetails(llList2Key(agents, 1), [OBJECT_POS] ), 0));

</lsl>


if you were to compute the distance between two agents.


Lists are sometimes strided, and then you also need to remember which item of a stride is to be interpreted as what. Your code becomes hard to write, hard to read and hard to modify. You can use defines and macros to avoid this.

The list used in example-1_B is not strided, simple and a local variable. Using a define and macros to make it useable is overdoing it, but I want to show you how it can be done.

You can put preprocessor directives anywhere into your source. They don´t need to be kept in seperate files. Traditionally, they had to be at the beginning of a line. This isn´t required anymore --- but do not indent them! They are not part of your code. They modify your code. You could see them as "metacode".

With example-1_B, four things can be defined to make the list of agents easier to use:


  1. a define for the stride of the list: "#define iSTRIDE_agents 1"
  1. a macro that gives the UUID of an agent on the list: "#define kAgentKey(x) llList2Key(agents, x)"
  1. a macro that gives the position of an agent on the list: "#define vAgentPos(x) RemotePos(kAgentKey(x))"
  1. a macro that gives the distance of an agent from the object: "#define fAgentDistance(x) llVecDist(here, vAgentPos(x))"


Those will be used for a loop that iterates over the list. This leads to example-1_C:


<lsl>

  1. include <lslstddef.h>


default { event touch_start(int t) { unless(llDetectedKey(0) == llGetOwner()) { return; }

vector here = llGetPos(); list agents = llGetAgentList(AGENT_LIST_PARCEL, []);

  1. define iSTRIDE_agents 1
  2. define kAgentKey(x) llList2Key(agents, x)
  3. define vAgentPos(x) RemotePos(kAgentKey(x))
  4. define fAgentDistance(x) llVecDist(here, vAgentPos(x))

int agent = Len(LIST_agents); while(agent) { agent -= iSTRIDE_agents; } }

event state_entry() { SLPPF(LINK_THIS, [PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_CONVEX]); } } </lsl>


Since the list is a local variable, the defines are placed after its declaration. You could place them somewhere else, maybe at the top of the source. Placing them inside the scope of the list makes clear that they are related to this particular list.

The define "iSTRIDE_agents" is the stride of the list, 1 in this case. Using "--agent;" in the loop instead of "agent -= iSTRADE_agents;" would be better, but it´s done for the purpose of demonstration. When you have a strided list, it is a good idea to always use a define for it: It doesn´t hurt anything and makes it easy to modify your source because there is only one place you need to change. With strided lists, use defines for the indices of stride-items so you don´t need to go through the macro definitions to make sure the indices are ok when you change the composition of the list. You can look at "projects/rfedip-reference-implementation/src/rfedipdev-enchain.lsl" in the [https://github.com/Ratany/lsl-repo git repository that accompanies this article] for a better example.

"kAgentKey" is a macro. It´s a define that takes parameters. You might think the parameter is an integer which is an index to the list in which the UUIDs of the agents are stored. It is not: The parameter is used an index. The preprocessor doesn´t know anything about lists or integers.

There are no types involved with macro parameters as are with variables: When you declare a function in LSL that takes parameters, you need to declare what type the parameters are of. Macro parameters don´t have a type. The preprocessor replaces stuff literally. You could use a vector or some random string as a parameter with "kAgentKey". Cpp would do the replacement just fine. It replaces text like a search-and-replace operation you perform with your favourite editor does.

Unfortunately, you cannot define defines or macros with defines: "#define NEWLIST(name, stride) #define list name; #define iSTRIDE_##name stride" will cause cpp to stop proprocessing and to print an error message. Maybe I´ll write a perl script to pre-preprocess my sources ... that might be easier than learning | m4.

"vAgentPos" and "fAgentDistance" are also macros and work the same way as "kAgentKey". The code to figure out which agent is the one furthest away from the object is simple, and I´ll skip over it. It gets interesting when the movement is implemented, so here is example-1_D, the complete source:


<lsl>

  1. include <lslstddef.h>


  1. define fUNDETERMINED -1.0
  2. define fIsUndetermined(_f) (fUNDETERMINED == _f)
  1. define fMetersPerSecond 3.0
  2. define fSecondsForMeters(d) FMax((d), 0.03)
  1. define fRANGE(_f) (_f)
  2. define vMINRANGE(_f) (<_f, _f, _f>)
  3. define fRandom(_fr) (llFrand(fRANGE(_fr)) - llFrand(fRANGE(_fr)))
  4. define vRandomDistance(_fr, _fm) (<fRandom(_fr), fRandom(_fr), fRandom(_fr)> + vMINRANGE(_fm))
  1. define fDistRange 3.5
  2. define fMinDist 0.5


default { event touch_start(int t) { unless(llDetectedKey(0) == llGetOwner()) { return; }

vector here = llGetPos(); list agents = llGetAgentList(AGENT_LIST_PARCEL, []);

  1. define iSTRIDE_agents 1
  2. define kAgentKey(x) llList2Key(agents, x)
  3. define vAgentPos(x) RemotePos(kAgentKey(x))
  4. define fAgentDistance(x) llVecDist(here, vAgentPos(x))

float distance = fUNDETERMINED; int goto; int agent = Len(agents); while(agent) { agent -= iSTRIDE_agents;

float this_agent_distance = fAgentDistance(agent); if(distance < this_agent_distance) { distance = this_agent_distance; goto = agent; } }

unless(fIsUndetermined(distance)) { distance /= fMetersPerSecond; vector offset = vRandomDistance(fDistRange, fMinDist); offset += PosOffset(here, vAgentPos(goto)); llSetKeyframedMotion([offset, fSecondsForMeters(distance)], [KFM_DATA, KFM_TRANSLATION]); }

  1. undef AgentKey
  2. undef AgentPos
  3. undef AgentDistance

}

event state_entry() { SLPPF(LINK_THIS, [PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_CONVEX]); } } </lsl>


There isn´t much new to it. "unless" is defined in "lslstddef.h" and speaks for itself. "fUNDETERMINED" and "fIsUndetermined" speak for themselves, too: There are no negative distances between positions, and what "llVecDist()" returns is never negative. There may be no agents on the parcel, in which case the list of agents would be empty. It´s length would be 0 and the code in the while loop would not be executed. The variable "distance" would still have the value it was initialized with when it was declared. What the furthest agent is would be undetermined, and that´s exactly what the code tells you.

The movement of the object should look nice. It looks nice when the object always moves at the same speed. "llSetKeyframedMotion()" doesn´t use speed. You have to specify the duration of the movement. Since the distance between the object and agents it moves to varies, the duration of the movement is adjusted according to the distance. The minimum duration "llSetKeyframedMotion()" works with is 1/45 second. "fSecondsForMeters" makes sure that the duration is at least 1/45 second through the "FMax" macro which is defined in "lslstddef.h": "FMax" returns the greater value of the two parameters it is supplied with.

"vRandomDistance" is used to help with that the object doesn´t hit the agent it moves to and ends up at some random distance from them, with a minimum offset. "PosOffset" from "lslstddef.h" works with two vectors and gives the offset between them as a vector. It´s one of my favourite macros because I don´t need to ask myself anymore what "offset" is supposed to mean: "here - there" or "there - here". The offset is simply "PosOffset(here, there)" when it´s an offset from here to there and "PosOffset(there, here)" when it´s offset from there to here. "llSetKeyframedMotion()" uses offsets ...

The important thing here is this: Why divide the distance and introduce another vector for the offset when you could simply do it in one line like this:


<lsl> llSetKeyframedMotion([PosOffset(here, vAgentPos(goto) + vMINRANGE(0.5)), FMax(distance / fMetersPerSecond, 1.0/45.0)], [KFM_DATA, KFM_TRANSLATION]); </lsl>


That´s one thing all these macros are for, aren´t they? Yes they are --- and you need to be aware of what they turn into. Cpp is designed to preprocess code for a C or C++ compiler, and those are probalby a lot smarter than an LSL compiler. The C compiler optimizes "1.0/45.0" by replacing it with the result of the division. C has a ternary operator which makes life a lot easier not only for writing macros like "FMax": "#define MAX(a,b) (((a) > (b)) ? (a) : (b))" works for both integers and floats --- compared to "#define FMax(x, y) (((llFabs((x) >= (y))) * (x)) + ((llFabs((x) < (y))) * (y)))" and another one for integers.

The line from above turns into:


<lsl> llSetKeyframedMotion([( (llList2Vector(llGetObjectDetails(llList2Key(agents, goto), [OBJECT_POS] ),

                                      0) + (<0.5, 0.5, 0.5>)) - (here) ),
                     ( ( (llFabs( (distance / 3.0) >= (1.0 / 45.0) ) ) * (distance / 3.0) ) + ( (llFabs( (
                                 distance / 3.0) < (1.0 / 45.0) ) ) * (1.0 / 45.0) ) )],
                    [KFM_DATA, KFM_TRANSLATION]);

</lsl>


Letting aside that the code would be totally different when written in C, a C compiler would optimze that. I don´t know what the LSL compiler does.

Always be aware of what your macros turn into. The code from example-1_D turns into:


<lsl> distance /= 3.0; vector offset = ( < (llFrand((3.5)) - llFrand((3.5))), (llFrand((3.5)) - llFrand((3.5))),

                 (llFrand((3.5)) - llFrand((3.5))) > + (<0.5, 0.5, 0.5>));

offset += ( (llList2Vector(llGetObjectDetails(llList2Key(agents, goto), [OBJECT_POS] ),

                          0)) - (here) );

llSetKeyframedMotion([offset, ( ( (llFabs( ((distance)) >= (0.03) ) ) * ((distance)) ) + ( (

                                   llFabs( ((distance)) < (0.03) ) ) * (0.03) ) )], [KFM_DATA, KFM_TRANSLATION]);

</lsl>


It´s still awful to read. What did you expect? The source looks nice and clear because macros and defines are used, wich is one of the reasons to use them. More importantly, this code doesn´t employ numerous obsolete divisions because I checked the outcome and modifed my source.

Always check the script for what your macros turn into. It´s easy to check, just load the debugging version into your favourite editor. You can even use the built-in editor: change the "o" for an "i" and click on the "Edit" button as described earlier.

One thing is new in example-1_D: The defines that were used to make the list with the UUIDs of the agents usable are undefined with "#undef". It´s not necessary to do this, it´s tidy. As mentioned before, the list has local scope. That´s the reason I undefined the defines that relate to it. It ends their "scope" as well. You can create new defines with the same names outside this scope if you like. They are undefined and out of the way. Perhaps at some time, you include something into your source that happens to use defines with the same name. You would get an error message and have to go back to the perhaps old source and figure out where you can undefine them. It doesn´t hurt to undefine them right away and saves you time later.

Script memory and performance with cpp

You may have seen or written scripts that use variables as constants. Look at this example:


<lsl>

  1. include <lslstddef.h>


  1. define WITH_DEFINE_FLOAT 0
  2. define WITH_DEFINE_INT 0


  1. if WITH_DEFINE_FLOAT
  2. define ftest 1.0
  3. else

float ftest = 1.0;

  1. endif
  1. if WITH_DEFINE_INT
  2. define itest 1
  3. else

int itest = 1;

  1. endif


default { event touch_start(int t) { apf("---"); apf("the float is", ftest, "and the int is", itest, " with ", llGetFreeMemory(), " bytes free"); apf("the float is", ftest, "and the int is", itest, " with ", llGetFreeMemory(), " bytes free"); apf("the float is", ftest, "and the int is", itest, " with ", llGetFreeMemory(), " bytes free"); apf("the float is", ftest, "and the int is", itest, " with ", llGetFreeMemory(), " bytes free"); apf("the float is", ftest, "and the int is", itest, " with ", llGetFreeMemory(), " bytes free"); apf("the float is", ftest, "and the int is", itest, " with ", llGetFreeMemory(), " bytes free"); apf("the float is", ftest, "and the int is", itest, " with ", llGetFreeMemory(), " bytes free"); apf("the float is", ftest, "and the int is", itest, " with ", llGetFreeMemory(), " bytes free"); apf("the float is", ftest, "and the int is", itest, " with ", llGetFreeMemory(), " bytes free"); apf("the float is", ftest, "and the int is", itest, " with ", llGetFreeMemory(), " bytes free"); apf("---"); } } </lsl>


You can find this example in the git repository that accompanies this article as "projects/memtest/src/memtest.lsl".

It shows that the script needs 8 bytes more memory when you use either a float or an integer variable --- or both! --- instead of defines. That means when you declare a variable, declaring another one doesn´t make a difference, provided that the variables are of the types float and integer.

I give up artifical testing at this point. Either the test or the results must be flawed. One thing I wanted to know is whether it makes a difference if you have several lines with a number (like 1 or 1.0) vs. the same several lines with a variable instead of the number. This test doesn´t show that, perhaps due to optimizations the LSL compiler performs.

I use defines for constants --- not because it apparently may save 8 bytes, but because it makes sense. That you can switch between defines and variables for constants makes testing with "real" scripts easy.

Test with your actual script what difference it makes.

It seems obvious that you would waste memory when you use a macro that the preprocessor turns into a lengthy expression which occurs in the script multiple times. When it matters, test it; you may be surprised.

You can also try to test for performance. Provided you find a suitable place to set up your test objects and let the tests run for at least a week, you may get valid results. Such tests could be interesting because you may want to define a function instead of a macro --- or a function that uses a macro --- to save script memory when the macro turns into a lengthy expression. In theory, the macro would be faster because using a function requires the script interpreter to jump around in the code and to deal with putting parameters on the stack and with removing them while the macro does not. But in theory, declared variables use script memory.

When programming in C and you have a statement like


<lsl> if((A < 6) && (B < 20)) [...] </lsl>


the part '(B < 20)' will not be evaluated when '(A < 6)' evaluates to false. This is particularly relevant when you, for example, call a function or use a macro:


<lsl> if((A < 6) && (some_function(B) < 20)) [...] </lsl>


LSL is stupid in that it will call the function or evaluate the expression the macro turned into even when '(A < 6)' evaluates to false. It´s impossible for the whole condition to evaluate to true when '(A < 6)' is false, and evaluating a condition should stop once its outcome has been evaluated. Calling a function takes time, and the function even might be fed with illegal parameters because B could have an illegal value the function cannot handle, depending on what the value of A is:


<lsl> integer some_function(integer B) {

   llSay(0, "10 diveded by " + (string)B + " is: " + (string)(10 / B));
   return 10 / B;

}


default {

   touch_start(integer total_number)
   {
       integer B = 20;
       integer A = 5;
       if(A < 8) B = 0;
   
       if((A < 6) && (some_function(B) < 20))
       {
           llSay(0, "stupid");
       }
   }

} </lsl>


This example will yield a math error because some_function() is called even when it should not be called. Such nonsense should be fixed. Calling the function is an undesireable side effect. To work around it so that some_function() isn´t called, you need to add another "if" statement, which eats memory.

Depending on what the function or the macro does, your "if" statements can affect performance. This is independent of using a preprocessor, but since you may be tempted to use macros in your sources when you use a preprocessor, you may be tempted to use them in "if" statements and create more undisirable side effects than you otherwise would.

Avoid "if" statements which evaluate expressions that should not be evaluated.

Use Parentheses

When you look at this simple macro


<cpp>

  1. define times3(x) x * 3

</cpp>


it will multiply its parameter by 3. At least that´s what you think. When your source is


<lsl>

  1. define times3(x) x * 3

[...]

int a = 5; int b = times3(a); llSay(0, (string)b); [...] </lsl>


it will print 15. Most of the time, your source will be more like this:


<lsl>

  1. define times3(x) x * 3

[...]

int a = 10; int b = 5; llSay(0, times3(a - b)); [...] </lsl>


You would expect it to print 15, but it will print -5:


10 - 5 * 3 == 10 - 15 == -5


You need to use parentheses:


<cpp>

  1. define times3(x) (x) * 3

</cpp>


(10 - 5) * 3 == 5 * 3 == 15


You need even more parentheses, because:


<lsl>

  1. define plus3times3(x) (x) * 3 + 3

[...] int a = 10; int b = 5; int c = 20; int d = 15; llSay(0, (string)(plus3times3(a - b) * plus3times3(c - d)); </lsl>


(10 - 5) * 3 + 3 * (20 - 15) * 3 + 3 == 15 + 3 * 5 * 3 + 3 == 15 + 45 + 3 == 63


Imagine you defined 'plus3times3' without any parentheses at all. You´d waste hours trying to figure out why your script yields incredibly weird results.

To get it right:


<lsl>

  1. define plus3times3(x) ((x) * 3 + 3)

[...] int a = 10; int b = 5; int c = 20; int d = 15; llSay(0, (string)(times3(a - b) * times3(c - d)); </lsl>


((10 - 5) * 3 + 3) * ((20 - 15) * 3 + 3) == 324


There is quite a difference between 63 and 324. Use parentheses. I also always use parentheses with conditions because it makes them unambiguous:


<lsl> if(A || B && C || D > 500) [...] </lsl>


is ambiguous. What I meant is:


<lsl> if((A || B) && (C || (D > 500))) [...] </lsl>


That is not ambiguous. Since "&&" usually takes precedence over "||", it´s different from the version without parentheses. (It probably doesn´t matter in this case, but I´m not even trying to figure it out. It does matter in C because it has an effect on which of the expressions are evaluated and wich are not. Simply write what you mean, with parentheses, and don´t let LSL get you to develop a bad style.) When you have the above "if" statements in a macro, use parentheses around "A", "B", "C" and "D", for reasons shown above.

Save yourself the hassle. My LSL scripts tend to have lots of parentheses, a lot of them not strictly needed because they come from macro definitions. They don´t hurt anything. Not having them can hurt badly.

How to use cpp

Finally, there is the question of how to use cpp. Cpp has a lot of command line parameters, and one particularly useful when using cpp for LSL is '-P'. The manpage of cpp says:


  • -P Inhibit generation of linemarkers in the output from the preprocessor. This might be useful when running the preprocessor on something that is not C code, and will be sent to a program which might be confused by the linemarkers.


The LSL compiler is one of the programs that would be confused by line markers. You can see what the line markers look like when you preprocess one of your scripts with


<lsl> cpp your_script.lsl </lsl>


By default, cpp writes its output to stdout, i. e. it appears on your screen. Since it isn´t very useful to have your LSL scripts appear on your screen like that, you may want to redirect the output into a file:


<lsl> cpp -P your_script.lsl > your_script.i </lsl>


This creates, without the line markers, a file that contains your preprocessed source. This file is named "your_script.i".

It´s that simple.

You can now write a script and use defines and macros and preprocess it. You can log in with your sl client, make a box, create a new script in the box, open the script, click on the "Edit" button and replace the default script that shows up in your favourite editor with the one you wrote and preprocessed.

It´s not entirely convenient yet because you still need to replace the script manually. You may also want to include files into your script, like "lslstddef.h". You don´t want to keep another copy of "lslstddef.h" with all your scripts and include it with '#include "lslstddef.h"'. You only need one copy of "lslstddef.h", and it´s a good idea to keep it in a directory, perhaps with other headers you use.

Cpp has another parameter for this: '-I'. '-I' tells cpp where to look for files you include with something like '#include <lslstddef.h>'.

If you haven´t pulled (i. e. downloaded) the git repository that accompanies this article, please do so now.

When you look into the "projects" directory from the repository, you´ll find a few directories in it:


<lsl> projects |-- bin |-- example-1 |-- include |-- lib |-- rfedip-reference-implementation `-- template-directories </lsl>


The "bin" directory contains tools; "include" is for files like "lslstddef.h" which you may want to include into many of your sources. The "lib" directory is much like the "include" directory; the difference is that "include" is for header files which provide defines and macros, while the "lib" directory is for files parts of which will be included into your sources when you set up defines which get these parts included. Those parts can be functions or defines or macros, and they are put together in files depending on their purpose.

For example, "lib/getlinknumbers.lsl" provides functions related to figuring out the link numbers of prims. "getlinknumbers.lsl" is a library of such functions, macros and defines. Since it´s a library, it´s in the "lib" directory: "lib" as in "library".

The "template-directories" directory is a template which you can copy when you start writing a new script. It contains directories to put particular files, like the "src" directory where you put the source you´re writing. It also has a "lib" directory which can hold files you want to include into one or several of the sources in the "src" directory, but which are too specific to the particular project that you don´t want to put them into the general "projects/lib" or "projects/include" directory.

Now when preprocessing your source with cpp, you may want to include "lslstddef.h". Since you´re starting a new project, you first copy the template directories:


<lsl> [~/src/lsl-repo/projects] cp -avx template-directories/ your-script ‘template-directories/’ -> ‘your-script’ ‘template-directories/bin’ -> ‘your-script/bin’ ‘template-directories/dbg’ -> ‘your-script/dbg’ ‘template-directories/doc’ -> ‘your-script/doc’ ‘template-directories/lib’ -> ‘your-script/lib’ ‘template-directories/src’ -> ‘your-script/src’ ‘template-directories/tmp’ -> ‘your-script/tmp’ ‘template-directories/bak’ -> ‘your-script/bak’ ‘template-directories/Makefile’ -> ‘your-script/Makefile’ [~/src/lsl-repo/projects] </lsl>


Now you can use your favourite editor, write your script and save it as "projects/your-script/src/your-script.lsl":


<lsl>

  1. include <lslstddef.h>


default { event touch_start(int t) { afootell("hello cpp"); } } </lsl>


As described above, you run 'cpp -P your_script.lsl > your_script.i'. What you get looks like this:


<lsl> [~/src/lsl-repo/projects/your-script/src] cpp -P your_script.lsl > your_script.i your_script.lsl:1:23: fatal error: lslstddef.h: No such file or directory

#include <lslstddef.h>
                      ^

compilation terminated. [~/src/lsl-repo/projects/your-script/src] </lsl>


This tells you that cpp didn´t find "lslstddef.h". You need to tell cpp where to find it:


<lsl> [~/src/lsl-repo/projects/your-script/src] cpp -I../../include -P your_script.lsl > your_script.i [~/src/lsl-repo/projects/your-script/src] </lsl>


Using '-I', you tell cpp to look for files that are to be included in the directory '../../include'. Since you do this from within the directory 'projects/your-script/src', cpp will look into 'projects/include' --- where it can find "lslstddef.h".

You can now look at the outcome:


<lsl> [~/src/lsl-repo/projects/your-script/src] cat your_script.i default {

touch_start(integer t)
{
 llOwnerSay("(" + (string)( (61440 - llGetUsedMemory() ) >> 10) + "kB) ~> " + "hello cpp");
}

} [~/src/lsl-repo/projects/your-script/src] </lsl>


You may think that this is inconvenient. You do not want to type something like 'cpp -I../../include -P your_script.lsl > your_script.i' for every script. You don´t want to do this for every script when a file included by the source may have changed. You don´t want to do this for all the scripts that are part of your project just in case you have modified one of them, and you don´t want to forget to preprocess all the scripts that need to be preprocessed every time you modified something.

Fortunately, you don´t need to do that. There is a tool that can do it for you. It´s called "make".

Make

Very nicely said, Make "is a tool which controls the generation of executables and other non-source files of a program from the program's source files."


As you may have noticed, in the previous section of this article I have referred to "scripts" (LSL scripts) and "sources", without mentioning what the difference is. The source is what you write. It can be an LSL script that can be compiled by an LSL compiler right away. That´s probably what you have written until you read this article.

After reading this article, you may not want to write plain LSL scripts anymore. You may want to use a decent editor, a preprocessor and Make. What you´ll write is still source, but it may be source which cannot be compiled by an LSL compiler right away because it needs to go through a preprocessor first. That´s what I was trying to refer to as "source".

You could say that An LSL script is the output of a preprocessor that processes a source file. I don´t write LSL scripts anymore. I write source files that can be turned into LSL scripts. It´s much easier to create LSL scripts that way than it is to write them.

In the sense of Make, the LSL script falls under "other non-source files". Make isn´t used here to generate an | executable. LSL scripts are not exectutable, and apparently the LSL compiler compiles them into | byte code.

"Other non-source files" can also mean documentation. The provided makefile has entries that use pdflatex to create a PDF file from a LaTeX source and to copy the PDF into the project directory. I don´t write PDF files. I write source files that can be turned into PDF files. It´s much easier to create PDF files that way than it is to write them :)


Preprocessing "your-script.lsl" like in the previous section generates a "non-source file" from a source file. You have controlled the generation of the non-source file manually, which can be inconvenient.

More conveniently, Make can do this job for you. All you need for that is Make, and a so-called Makefile. It´s called Makefile because it tells Make what it needs to do to generate files from files, and under which conditions which files need to be created.

You do not need to understand Make or Makefiles to make creating LSL scripts easier. You find a suitable Makefile in the git repository that accompanies this article. The Makefile is "projects/make/Makefile".

This Makefile is symlinked into the template-directories. There is only one Makefile for all your projects. It works for all of them. This has the advantage --- or disadvantage --- that when you change the Makefile, the changes apply to all your projects.

Should you need to adjust the Makefile because a particular project has special requirements, remove the symlink to the Makefile from the projects´ directory. Copy make/Makefile into the projects´ directory and then modify the copy.


How to use Make

Enter the directory of the project you are working on and run 'make'. That´s all.

You can run make from within emacs: M-x compile. Emacs will show you the output in a buffer. If there are error messages, you can move the cursor to the error message and press enter and emacs shows you the line in the file that triggered the error message. To compile again, you can use 'recompile': M-x recompile. You can make a key-binding for that with something like '(global-set-key [C-f1] 'recompile)' in your ~/.emacs and recompile your source conveniently from within your favourite editor --- which by now is, of course, emacs --- by simply pressing Ctrl+F1.

Make "your-example.lsl"

Assume you want to try out the "your-script.lsl" source from above. You need to make an LSL script which you can put into an object for this. Suppose you make a box and name it "testbox".

Enter the directory for the project ("/projects/your-script/") and run 'make':


<lsl> [~/src/lsl-repo/projects/your-script] make COMPILING src/your-script.lsl

 8 153 dbg/your-script.i.clean

[~/src/lsl-repo/projects/your-script] </lsl>


This has done a few things which it doesn´t tell you about. It merely tells you that it was compling --- you could say preprocessing instead since your script isn´t really compiled --- and that the resulting LSL script has 8 lines and 153 characters.

You probably can´t wait to see the results now. Two non-source files were generated from the source files:


  1. "projects/your-script/dbg/your-script.i" and
  1. "projects/your-script/bin/your-script.o".


The first one is the so-called debugging version, the second one is the version you put into the "textbox" as a script. The second one is "compressed" and not easy to read. You can put either into the "testbox" as a script.

If you remember the perl script that automatically replaces the scripts in the built-in editor


With the Makefile from the repository, Make does a bit more than only running cpp. It will show you what it does when you run 'make -n', and the '-B' option tells Make to do everything it would, much as if you were running it for the first time for your new source:


<lsl> [~/src/lsl-repo/projects/your-script] make -Bn echo "COMPILING src/your_script.lsl" echo -n "//" > dbg/your_script.i cpp -I../lib -I./lib -I../include -DVERSION='version 0.00' -P src/your_script.lsl | md5sum >> dbg/your_script.i cpp -I../lib -I./lib -I../include -DVERSION='version 0.00' -P -DTIME=`date +0x%H%M%S` -DDATE=`date +0x%Y%m%d` src/your_script.lsl >> dbg/your_script.i sed -f ../clean-lsl.sed dbg/your_script.i > dbg/your_script.i.clean wc -cl dbg/your_script.i.clean astyle --style=allman -f < dbg/your_script.i.clean > dbg/your_script.i cat dbg/your_script.i | sed -f ../clean-lsl.sed | ../bin/i2o.pl `basename dbg/your_script.i` > bin/your_script.o etags src/your_script.lsl ../lib/geometry.lsl ../lib/getlinknumbers.lsl [~/src/lsl-repo/projects/your-script] </lsl>


Make runs cpp, sed, wc, astyle, etags (which comes with emacs), cat, echo and md5sum.

It also runs i2o.pl, which is a perl script. You can find it in the git repository that accompanies this article in the projects/bin directory.

i2o.pl comes from the source of the Open Stargate Network stargates. It´s slighly modified. The Makefile is from the same source and has also been modified. The whole idea of preprocessing the source with cpp to generate LSL scripts is from there. It´s not my idea, I´m merely using it and writing this article about it.

When you have successfully run 'make', you will find files in the 'projects/your-script/dbg' and 'projects/your-script/bin' directories. 'dbg' stands for 'debug', 'bin' for 'binary'. The files in these directories are LSL scripts that have been generated from your source in the 'projects/your-script/src' directory --- in this case, from 'projects/your-script/src/your-script.lsl'.

You will find "your-script.i" and "your-script.i.clean" in the 'dbg' directory. "your-script.i.clean" is an intermediate file which was created because it is not exactly a good idea to let sed overwrite its input file with its output. You can delete "your-script.i.clean" if you like, and/or modify the Makefile to delete it for you.

By filtering the output of cpp with sed, a few unwanted things are removed from the LSL scripts. The things removed with sed are introduced through the way I´m using macros which are defined in "lslstddef.h". Some of those lead to closing-braces followed by a semicolon ("};"), to double-semicolons (";;") or to an otherwise emtpy line with only a semicolon (";"). For example:


<cpp>

  1. if DEBUG0
  2. define DEBUGmsg0(...) DEBUGmsg(__VA_ARGS__)
  3. else
  4. define DEBUGmsg0(...)
  5. endif

</cpp>


This makes debug messages (of debug level 0) disappear when 'DEBUG0' does not evaluate to true. The source may look like this:


<lsl>

  1. include <lslstddef.h>

// [...] int a = Len(lChains); DEBUGmsg0("there are", a / iSTRIDE_lChains, "chains"); // [...] </lsl>


The output of cpp, when 'DEBUG0' is not true, looks like this:


<lsl>

integer a = llGetListLength(lChains);
;

</lsl>


The debug message disappears because it´s defined to disappear. But the semicolon at the end of the line is not part of the macro definition because when a line doesn´t end with a semicolon, emacs indents the following lines in an undesired way: That the semicolon is missing makes emacs figure that the line continues on the next line, which it doesn´t. If the semicolon was part of the macro definition, I´d still have to put it in to get the indentation as desired. It only would create a line with two semicolons when the debug message does not disappear. So when the debug message disappears, the semicolon remains. Sed removes it by deleting the whole line.

Sed "knows" what to do because it is instructed by a sed-script. You can find this sed-script as "projects/clean-lsl.sed".

Wc is a nice tool to give you statistics about text files. It can count words, bytes, lines etc.. It´s purely informational; I put it into the Makefile because I wanted to see what difference the debug mesages make for the size of the file and number of lines. You can comment it out in the Makefile, or remove it from the Makefile in case you don´t want it.

The same goes for astyle, it´s not required. It reformats the code so that it´s easier to read than the output of cpp. It´s useful when you want to put the LSL script generated from your source on wiki pages, and for debugging.

i2o.pl "compresses" the LSL script. It removes whitespace and line end-markers, and it shortens the names of variables. One advantage is that you may get a script which uncompressed has more than 64kB to work because it fits into "a script": The editor built into sl clients (or, more likely, the server does it) cuts off anything beyond 64kB when you upload something into a notecard or into a script. Another advantage is that your script may need less memory. My unverified impression is that "compressed" scripts even run a tiny bit faster, though that seems unlikely since they are compiled to bytecode just like uncompressed ones. Test it if you like --- since you got less to upload when the script is "compressed", in theory the "compressed" scripts don´t take as long to upload as uncompressed ones and you can test them faster. The server may even experience not so many difficulties.


Ratany Resident 15:12, 9 January 2014 (PST)