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

From Second Life Wiki
Jump to navigation Jump to search
 
(17 intermediate revisions by 2 users not shown)
Line 1: Line 1:
==How to make creating LSL scripts easier==


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


'''This article is currently being worked on.'''
One of these tools is a preprocessor called cpp.  It comes with [http://gcc.gnu.org/ GCC].  Among the tools mentioned in this article, cpp is the tool which can make the biggest difference to how you write your sources and to how they look.




==How to make writing LSL scripts easier==
The idea of using cpp comes from the [http://opengate.ma8p.com/open9.tar.gz source] of the [http://ma8p.com/~opengate/ Open Stargate Network] stargates.


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.


'''This article is currently being worked on.'''


This article is accompanied by a [git://dawn.adminart.net/lsl-repo.git repository]. It is recommended that you pull (download) the repository.  The repository contains some example sources used with this article, a few perl scripts and a Makefile.  They are organised in a directory structure which they are designed to work with.  You will have your own build system for LSL scripts when you have pulled this repository.


===About===
The repository does not contain basic standard tools like perl, cpp, make or an editor.  This article does not attempt to tell you how to install these.  You know better how to install software on your computer than I do, and how you install software also depends on which operating system you are using.


This article is about tools you can use to make creating LSL scripts
I´m using Linux, currently Gentoo.
easier.  It tries to explain how to combine and use them for this very
purpose.


The idea of using cpp comes from the
Documentation is plentiful. You may find different software that does the same or similar things and may work better for you or not.
[http://opengate.ma8p.com/open9.tar.gz source] of the
[http://ma8p.com/~opengate/ 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
==Editing this Article==
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.


This article is written in my favourite editor.  When I update the
Unfortunately, this means that your modifications may be lost when I update the article and don´t see them.
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
'''Please use the "discussion" page of this article to post contributions or to suggest changes so they can be added to the article.'''
update the article and don´t see them.
 
==Repository==
 
It´s probably more fun to be able to try out things yourself while reading this article.  If you want to do so, pull the [git://dawn.adminart.net/lsl-repo.git repository].  You can run 'git clone git://dawn.adminart.net/lsl-repo.git' to get it.
 
Once cloned, you have a directory called "lsl-repo".  Inside this directory, you have another one called "projects".
 
You can put the "projects" directory anywhere you like, and you can give it a different name if you want to.
 
Inside the "projects" directory, you have more directories like this:
 
 
<source lang="bash">
.
|-- bin
|-- clean-lsl.sed -> make/clean-lsl.sed
|-- example-1
|-- include
|-- lib
|-- make
|-- memtest
|-- rfedip-reference-implementation
|-- template-directories
`-- your-script
</source>
 
 
When you rename or move "bin", "include", "lib", "make" or "clean-lsl.sed", your build system will not work anymore (unless you edit some files and make adjustments).  Don´t rename or move them.
 
'''Do not use names for directories or files that contain special characters or whitespace.''' Special characters ''may'' work, whitespace probably doesn´t.  Maybe it works fine; I haven´t tested.
 
Suppose you want to try out "example-1".  Enter the directory "example-1" and run 'make':
 
 
<source lang="bash">
[~/src/lsl-repo/projects/example-1] make
generating dependencies: dep/example-1.di
generating dependencies: dep/example-1.d
preprocessing src/example-1.lsl
  39 1202 dbg/example-1.i.clean
[~/src/lsl-repo/projects/example-1]
</source>


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


===Tools===
You will find the LSL script as "projects/example-1/bin/example-1.o". A debugging version is "projects/example-1/dbg/example-1.i".  When you modify the source ("projects/example-1/src/example-1.lsl") just run make again after saving it, and new versions of the script will be created that replace the old ones.


This section tries to give an overview of some tools you can useIt
Some files are generated in the "dep" directory.  They are needed by the build system to figure out when a script needs to be rebuilt and whether entries need to be added to a look-up table or not.  You can ignore thoseA file is generated in the "make" directory.  The file is needed to automatically replace scripts with the script editor built into your sl client, which will be described later.
does not tell you how to install them.


It´s your computer you´re reading this article with, and you know
Now you can log into sl with your sl client.  Make a box, create a "New Script" in the box and open the "New Script".  It shows up in the built-in editor.  Delete the script in the built-in editor and copy-and-paste the contents of "projects/example-1/bin/example-1.o" into the built-in editor.  Then click on the "Save" button of the built-in editor.  (There is a way to do this kind of script-replacement automatically. It will be explained later.)
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
The "template-directories" is what you copy when you want to write your own source.  Copy it with all the files and directories that are in itSuppose you make a copy of the "template-directories" and name the copy "my-test". You would then put your own source into "my-test/src", like "my-test/src/my-test.lsl".
distributionsI´m using [http://fedoraproject.org/ Fedora], so to
install [http://www.gnu.org/software/emacs/ 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====
Of course, you can have several source files in "my-test/src".  For all of them, scripts will be created.  The "my-test" directory is the project directory for your project "my-test".  When you are working on an object that requires several scripts, you´ll probably want to keep them all together in the project directory you made for this object.


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
If you get an error message when you run 'make', you are probably missing some of the tools the build system uses. Install the missing tools and try again.
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
==Tools you need for the build system==
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:
Required tools:




* You can use a decent editor!
* basename: [http://www.gnu.org/software/coreutils/ coreutils]


* You have all your sources safely on your own storage system.
* cpp: [http://gcc.gnu.org/ GCC]


* 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".
* echo: [http://www.gnu.org/software/coreutils/ coreutils]


* You don´t need to be logged in to work on your sources.
* editor: your favourite editor, [http://www.gnu.org/software/emacs/ Emacs] is recommended


* You can preprocess and postprocess and do whatever else you like with your sources before uploading a script.
* make: [http://www.gnu.org/software/make/ Make]


* You can share your sources and work together with others because you can use tools like [http://git-scm.com/ git] and websites like [https://github.com/ github].  (Even when you don´t share your sources, git is a very useful tool.)
* perl: [http://www.perl.org/ | Perl]


* How fast you can move the cursor in the editor does not depend on the FPS-rate of your sl client.
* sed: [http://sed.sourceforge.net/ sed]


* 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.
Optional tools:


* And yes, you can use a decent editor :)


=====Emacs=====
* astyle: [http://astyle.sourceforge.net/ astyle]


[http://www.gnu.org/software/emacs/ Emacs] is my favourite editor.  It
* etags: [http://www.gnu.org/software/emacs/ Emacs]
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======
* pdflatex: [http://tug.org/texlive/ TeX Live]


Emacs comes with syntax highlighting for many programming languages.
* rm: [http://www.gnu.org/software/coreutils/ coreutils]
It doesn´t come with syntax highlighting for LSL, but LSL syntax
highlighting modes for emacs are available.


You can use [[Emacs_LSL_Mode]].
* wc: [http://www.gnu.org/software/coreutils/ coreutils]


A mofied version of lsl-mode is in the
[https://github.com/Ratany/lsl-repo 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======
You can replace "make/Makefile" with "make/Makefile-no-optional" and try without the optional tools.  You can make "echo" optional, but automatically replacing scripts won´t work unless you can find a replacement for "@echo "// =`basename $@`" > $@" in the Makefile. Since perl is required, you could replace "echo" (and "basename" and "sed") with perl scripts.  You can probaly make further modifications so you won´t need perl, but what do you do without it?


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======
When you´re messing with [http://www.microsoft.com Windoze], you may want to check out [http://cygwin.com/ Cygwin].  When you´re stuck with Apple, maybe [https://github.com/kennethreitz/osx-gcc-installer this repository] helps.


Emacs can support [http://git-scm.com/ git] (and other CVS systems).
However, under those conditions, you might be better off with a minimal [http://www.debian.org/ Debian] installation in a [http://en.wikipedia.org/wiki/Virtual_machine VM] and installing needed packages from thereLast time I tried, Debian still let you install a minimal system and didn´t force you to use a GUI version of the installer.  Fedoras´ installer sucks, and with [https://www.archlinux.org/ archlinux], you need to know what you´re doing.
You may need to install additional packages for it.  I haven´t tried
out any of them yet.


=====Other Editors=====
In case you want to upgrade from either Macos or Windoze, Fedora is a good choice.


In case you find that you don´t get along with emacs, you may want to
==Editor==
check out this [[LSL_Alternate_Editors|page about editors]].  Try
[http://en.wikipedia.org/wiki/Vi vi], or vim, instead.


You don´t need to try any other editors.
To create scripts, you do need an editor to write your sources. 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.
[http://en.wikipedia.org/wiki/User:Dozen/E_(text_editor_family)#EPM_.28OS.2F2.29 EPM]
was nice, but it crashed all the time.


In any case, use an editor you are happy with because it does what you
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 compileDon´t use it for anything else.
want.  An editor is one of the most essential tools.  You want your
tools to work the way you want them toYou don´t want tools that get
into your way.


====Automatically replacing Scripts====
Not using the buit-in script editor has several advantages like:


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.


[[Image:Cpp-wiki-builtineditor.jpg|thumb|Built-in Editor with "Edit" button]]
* You can use a decent editor!


Open a script in your sl client and click on the edit button of the
* You have all your sources safely on your own storage system.
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.


[[Image:Cpp-wiki-externaleditor.jpg|thumb|Debug Setting "ExternalEditor"]]
* 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".


Your favourite editor should now start and let you edit the script.
* You don´t need to be logged in to work on your sources.
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
* You can preprocess and postprocess and do whatever else you like with your sources before uploading a script:  You can use a build system.
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
* You can share your sources and work together with others because you can use tools like [http://git-scm.com/ git] and websites which will host your repositories for freeEven when you don´t share your sources, git is a very useful tool to keep track of the changes you're making.
offlineWhen 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
* Your window manager handles the windows of your editor so that you are not limited to the window of your sl client which doesn´t do a great job when it comes to managing windowsYou are not limited to a single monitor, and how fast you can move the cursor in the editor does not depend on the FPS-rate of your sl client.
built-in editor.  It would let the sl server compile the script and
show you the error messages, if anyWhen 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
* 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 ...
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
* 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.
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
* And yes, you can use a decent editor, even your favourite one :)
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 [http://www.perl.org/ perl] script.  It´s written in perl
because perl is particularly well suited for the purpose.  It looks
like this:


You can use any editor you like.  An editor is essential and one of your most important tools.  When your editor sucks, you cannot be productive.  Use the best editor you can find.


<perl>
===Emacs===
#!/bin/perl


[http://www.gnu.org/software/emacs/ Emacs] is my favourite editor. Give it a try, and don´t expect to learn everything about it within a day.  It´s enough when you can move the cursor around, type something and load and save files.  Over time, you will learn more just by looking up how to do something and trying it.  You will find ''your'' way of doing what you need and adjust emacs to what ''you'' want --- or you don´t and use it as it is.  Lots of documentation is available.


# This program is free software: you can redistribute it and/or
By default, the cursor keys move the cursor. Ctrl+xf loads a file into a new buffer and Ctrl+xs saves it.  Ctrl+xb lets you switch to another bufferThese are usually written as C-x-f, C-x-s and C-x-b.
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSESee the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this programIf not, see
# <http://www.gnu.org/licenses/>.


Emacs is worthwhile to learn.  It´s the only editor you need to learn because you don´t need any others.  It has been around since a long time.  If you had learned it twenty-five years ago, that would have saved you the time wasted with other editors which nowadays may not even exist anymore.


# usage: replace.pl <filename>
It doesn´t hurt to give it a serious try.
#
# replace file A with file B when the first line of file A is a key
# found in a look-up table; file B is specified by the value of the
# key
#
# Entries in the look-up table are lines.  Each line holds a key-value
# pair, seperated by a colon and a space: 'key: value'.
#


====Syntax Highlighting====


use strict;
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.
use warnings;
use autodie;


use File::Copy;
[[Image:Cpp-wiki-syntax-higlighting.jpg|thumb|Syntax highlighting in emacs with the lsl-mode from the repo]]


You can use [[Emacs_LSL_Mode]].


# the editor to use
A mofied version of lsl-mode is in the [git://dawn.adminart.net/lsl-repo.git 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) to give you better syntax highlighting with it.
#
# Note: Use an editor that waits before it exits until you are
# finished editing the fileYour sl client stops monitoring the file
# when the editor exits (or forks off into the background) before
# you´re done editing, and it may not replace the contents of its
# built-in editor with the contents of the file you´re editing.
#
my $editor = "emacsclient";
#
# "-c" makes emacsclient create a new frame.  If you start your
# favourite editor without such a parameter, you want to remove
# $editorparam here an in the 'start_editor' function.
#
my $editorparam = "-c";


====Tags====


# a wrapper function to start the editor
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.
#
sub start_editor
  {
    my (@files_to_edit) = @_;


    system($editor, $editorparam, @files_to_edit);
The build system creates a so-called tags-file for you.  Emacs can read it automatically. Esc+. (M-.) lets you enter a tag you want to go to and takes you there.
  }


====Git====


# unless the filename given as parameter is *.lsl, edit the file
See [http://www.gnu.org/software/emacs/tour/ this page]:  A number of version control systems are supported, and [http://git-scm.com/ git] is one of them.  Additional packages are available for this --- I haven´t tried any of them yet.
#
unless($ARGV[0] =~ m/.*\.lsl/)
{
  start_editor($ARGV[0]);
  exit(0);
}


====Customizing Emacs====


# the file name of the lookup table; specify an absolute path here
The [git://dawn.adminart.net/lsl-repo.git git repository that accompanies this article] has a directory "emacs".  It is recommended that you copy this directory to a suitable location like  your home directory.
#
my $table = "/absolute/path/to/replaceassignments.txt";


Two files are in this directory, "emacs" and "lsl-mode.el".  "lsl-mode.el" provides a syntax highlighting mode for emacs.  When you start emacs, it loads a file, usually "~/.emacs", and evaluates its contents.  You could say it´s a configuration file.


# read the first line of the file; unless it matches a pattern like
That´s what the file "emacs" in the "emacs" directory is.  You can move this file to "~/.emacs" or take a look at it and add what you need from it to the "~/.emacs" file you may already have when you are already using emacs.
# "// =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);
  }


"~/.emacs" contains instructions that make emacs load a file called "~/emacs/lsl-mode.el".  You compile this file from within emacs with M-x byte-compile-file.  Compiling it creates "~/emacs/lsl-mode.elc".  Compiling "~/emacs/lsl-mode.el" is recommended, and once you have compiled it, you need to edit "~/.emacs" and change "(load "~/emacs/lsl-mode.el")" to "(load "~/emacs/lsl-mode.elc")".


# look up the key in the lookup table
Without using lsl-mode.el (or lsl-mode.elc), syntax highlighting for your sources when you create LSL scripts won´t be as nice, so you really want to do this.
#
# the key is looked up for *.o and *.i so that only one entry per
# script is needed in the lookup table to cover both *.i and *.o,
# 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))
====Line Numbers====
  {
    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
Line numbers have become an anachronism.  They were used to create what was called "spaghetti code" with statements like "if(A) then goto 100;". Seriously.  There was no other way.  It helped to get you to think straight as a programmer.  Events are horrible when you think straight, and I don´t like them. They are worse than Makefiles. Seriously. But there is no other way.
    # the file and the table
    #
    if(($replacementfile =~ m/.*\.o$/) || ($replacementfile =~ m/.*\.i$/))
      {
copy($replacementfile, $ARGV[0]);
      }
  }
else
  {
    start_editor($ARGV[0], $table);
  }
</perl>


The built-in script editor displays line numbers.  Emacs can do that, too.  There´s a mode for it, called linum-mode.  Try M-x linum-mode.  After all, line numbers can be very useful for reference.


You´ll find this script in the
When you need to add line numbers to the contents of a buffer, look  [http://www.emacswiki.org/emacs/NumberLines here].
[https://github.com/Ratany/lsl-repo 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
===Other Editors===


* otherwise, look at the first line of the file
In case you find that you don´t get along with emacs, you may want to check out this [[LSL_Alternate_Editors|page about editors]].  There is also a page about [[LSL_Alternate_Editors|alternate editors]] and build systems/IDEs.


* 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
Try [http://en.wikipedia.org/wiki/Vi vi], or vim, instead.  You don´t need to try any other editors.  Your time is better spent on learning emacs or vi.


* look up the key in a look-up table (a file) that associates the key with a value
==Replacing Scripts==


* 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
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.


* 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
===Manually Replacing Scripts===


[[Image:Cpp-wiki-builtineditor.jpg|thumb|Built-in Editor with "Edit" button]]


Entries in the table are so-called key-value pairsThe table looks
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 setDon´t panic!  Set this debug setting so that your favourite editor is used, and click on the edit button again.
like this:


[[Image:Cpp-wiki-externaleditor.jpg|thumb|Debug Setting "ExternalEditor"]]


<lsl>
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.
example-script.o: /path/to/bin/example-script.o


//
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 a comment
//


foobar-script.o: /path/to/bin/foobar-script.o
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.
</lsl>


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.


Note: The space between the colon and the file name is mandatory
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.
(because the perl script is written like that).


This means when you have a script in the built-in script editor like
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 loose 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 (or a magnifying glass with a non-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.


<lsl>
===Automatically Replacing Scripts===
// =example-script.o


default
A program that replaces the script needs to know which script must be replaced with which file.  To let the program know this, a useful comment is put into the script, and a look-up table is used.  The comment tells the program the name of the script.  The look-up table holds the information which script is to be replaced with which file.  The build system puts the comment into the script and creates entries in the look-up table.  You just click the "Edit" button to replace the script.
{
state_entry()
{
Say(0, "test");
}
}
</lsl>


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 [http://www.perl.org/ perl] script.  It´s written in perl because perl is particularly well suited for the purpose.


and click on the "Edit" button of the built-in script editor, the
You´ll find this script in the [git://dawn.adminart.net/lsl-repo.git git repository that accompanies this article] as "projects/bin/replace.pl". It works like this:
script will automatically be replaced with the contents of the file
"/path/to/example-script.o".


To make this work, you need to:


* 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


* modify the perl script to start your favourite editor (in the unlikely case that your favourite editor isn´t emacs)
* otherwise, look at the first line of the file


* 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";')
* when the line starts with "// =", treat the rest of the line from there as a key; otherwise start your favourite editor to edit both the file and the look-up table


* change the debug setting "ExternalEditor" so that your sl client starts above perl script instead of your favourite editor
* look up the key in the look-up table


* create the look-up table (the table can be empty)
* 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


* make sure that your script starts with a line like "// =example-script.o"
* when the key is not found in the table, start your favourite editor to edit both the file and the look-up table


* try out the perl script:


To make this work, you need to:


'''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
* modify the perl script to start your favourite editor (in the unlikely case that your favourite editor isn´t emacs)
to see error messages you may get when running the perl script:


* change the debug setting "ExternalEditor" so that your sl client starts the perl script instead of your favourite editor


<lsl>
* make sure that your LSL script starts with a line like "// =example-script.o" --- the build system does this for you
[~/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>


* make sure that the look-up table exists --- the build system does this for you


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
To modify the perl script to use your favourite editor, edit it and change "use constant EDITOR => "emacsclient";" --- and perhaps "use constant EDITORPARAM => "-c";".  There are comments in the script explaining this.
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
When you have your sl client start the perl script, it is difficult or impossible to see error messages you may get when running the perl script.  If there is an error, it will probably seem as if nothing happens when you click on the "Edit" button.
key (i. e. "example-script.o") in the table.  You can then edit both
the script and the tableThis 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
One way to test if it works is to make sure that the look-up table does not exist and then to click on the "Edit" button of the built-in script editor in your sl client.  If the look-up table exists, you find the table as "projects/make/replaceassignments.txt"When your favourite editor starts to let you edit both the file your sl client saved and the (empty) look-up table, it is very likely that it´s working.
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 scriptIt´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
The build system fills the look-up table for you.  It adds entries for new scripts as needed when you run 'make'.  It does not delete entries from the tableTo make such entries, the perl script "projects/bin/addtotable.pl" in the [git://dawn.adminart.net/lsl-repo.git git repository that accompanies this article] is used.
scripts and versions that are not compressedThe "compressed"
versions are named *.o and the not-compressed versions are named *.i.


The "compressed" versions can be pretty unreadable, which can make
The first entry in the table that matches is usedIf you plan to have multiple scripts with the same name, you may need to adjust addtotable.pl, replace.pl and the Makefile.
debugging difficultThe 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,
The build system 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.
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 editorSwitch 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
The "compressed" version 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.)
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
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 editorSwitch the "i" back to an "o", click the "Edit" button and the "compressed" version is put back.
lookup-tableThey are automatically added as needed when you run
'make' (see below).


====A Preprocessor====
==A Preprocessor: cpp==


The [http://en.wikipedia.org/wiki/Preprocessor Wikipedia article]
The [http://en.wikipedia.org/wiki/Preprocessor 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 [http://en.wikipedia.org/wiki/C_(programming_language) C] (and C++) compilers is a very useful tool.
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
[http://en.wikipedia.org/wiki/C_(programming_language) C] (and C++)
compilers is a very useful tool.


The particular preprocessor I´m using is called "cpp" and comes with
The particular preprocessor I´m using is called "cpp" and comes with
[http://gcc.gnu.org/ GCC].
[http://gcc.gnu.org/ GCC].


=====What a preprocessor does=====
===What a preprocessor does===


A preprocessor enables you:
A preprocessor enables you:
Line 540: Line 309:




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




Line 569: Line 337:




There are probably lots of advantages that don´t come to mind at the
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.
moment.  You can be creative and find uses and advantages I would
never think of.


=====How cpp works=====
===How cpp works===


Some extensive documentation about cpp can be found
Some extensive documentation about cpp can be found [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 on
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.
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
In the following, the source for the script will be developed piece by 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".
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
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.
agent is the furthest away and a a way of moving to the agent.


In this case, [[LlSetKeyframedMotion|keyframed motion]] (KFM) is a
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()]].
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
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.
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
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.
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
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 an object or prim or agent.  Another one is "SLPPF()", a shortcut for llSetLinkPrimitiveParamsFast() because llSetLinkPrimitiveParamsFast() is awfully long.
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:
This makes for a source (example-1_A) that looks like this:




<lsl>
<source lang="lsl2">
#include <lslstddef.h>
#include <lslstddef.h>


Line 647: Line 376:
}
}
}
}
</lsl>
</source>




The '#include' in the first line is a so-called preprocessor
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.
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
"#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.
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.
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.
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
"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.
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
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.
"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.


[[Image:Cpp-wiki-example-1_A.jpg|thumb|Easier to read]]
[[Image:Cpp-wiki-example-1_A.jpg|thumb|Easier to read]]


The LSL compiler wouldn´t compile the source, not only because it
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:
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>
<source lang="cpp">
#define event
#define event
</cpp>
</source>




By including "lslstddef.h", cpp knows this and replaces all occurances
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:
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>
<source lang="cpp">
#ifdef event
#ifdef event
#define test 1
#define test 1
#endif
#endif
</cpp>
</source>
 


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".  With conditionals like "#ifdef", "#if" or "#ifndef", you can have code from your source put into the script depending on what is defined.  For example, you can disable a block of code simly by putting "#if 0" at the start of the block and "#endif" at the end of the block.  That´s easier than commenting out the whole region.


This would create a define named "test" only if "event" is defined.
The library files in "projects/lib" in the [https://github.com/Ratany/lsl-repo git repository that accompanies this article] make use of conditionals.  The whole library file is included into the source, and defines are used to decide which functions from the libraries will be in the resulting script.  You can see in "projects/rfedip-reference-implementation/src/rfedipdev-enchain.lsl" how it´s done.
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
The example script needs to set the physics type and to check if it was the owner of the object who touched it.  It needs to know the position of the object the script is in and what agents are around. Add all this to the source, which is now example-1_B:
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>
<source lang="lsl2">
#include <lslstddef.h>
#include <lslstddef.h>


Line 748: Line 440:
}
}
}
}
</lsl>
</source>




Line 754: Line 446:




<cpp>
<source lang="cpp">
#define event
#define event
#define SLPPF                          llSetLinkPrimitiveParamsFast
#define SLPPF                          llSetLinkPrimitiveParamsFast
</cpp>
</source>




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




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




<lsl>
<source lang="lsl2">
llList2Vector(llGetObjectDetails(llList2Key(agents, 0), [OBJECT_POS]), 0);
llList2Vector(llGetObjectDetails(llList2Key(agents, 0), [OBJECT_POS]), 0);
</lsl>
</source>




Line 779: Line 467:




<lsl>
<source lang="lsl2">
llVecDist(llList2Vector(llGetObjectDetails(llList2Key(agents, 0), [OBJECT_POS] ), 0),
float d = llVecDist(llList2Vector(llGetObjectDetails(llList2Key(agents, 0), [OBJECT_POS] ), 0),
           llList2Vector(llGetObjectDetails(llList2Key(agents, 1), [OBJECT_POS] ), 0));
           llList2Vector(llGetObjectDetails(llList2Key(agents, 1), [OBJECT_POS] ), 0));
</lsl>
</source>




Line 788: Line 476:




Lists are sometimes strided, and then you also need to remember which
Lists are sometimes strided, and then you also need to remember which item of a stride is to be interpreted as what.  You can use defines and macros to avoid this. If you were to compute the distance between two agents, you could use defines like:
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.
<source lang="cpp">
#include <lslstddef.h>
 
#define kAgentKey(x)      llList2Key(agents, x)
#define fAgentsDist(a, b)  llVecDist(RemotePos(a), RemotePos(b))
</source>
 
 
Instead of the lengthy expression above, your source would then be:
 
 
<source lang="lsl2">
float d = fAgentsDist(0, 1);
</source>
 
 
How much easier to write and read and maintain is that?  How much less prone to errors is it?


The list used in example-1_B is not strided, simple and a local
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.
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
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".  They do not belong indented.
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
With example-1_B, four things can be defined to make the list of agents easier to use:
agents easier to use:




Line 816: Line 513:




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




<lsl>
<source lang="lsl2">
#include <lslstddef.h>
#include <lslstddef.h>


Line 853: Line 549:
}
}
}
}
</lsl>
</source>
 
 
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 source that uses a strided list.
 
"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 parameters of functions:  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 and not give you error messages.  It replaces text like a search-and-replace operation you perform with your favourite editor does.  This can sometimes be limiting:
 
 
<source lang="lsl2">
#include <lslstddef.h>
 
#define component(f)  ((string)(f))
#define components(v)  llSay(0, component(v.x) + component(v.y) + component(v.z))
 
 
default
{
event touch_start(int t)
{
vector one = ZERO_VECTOR;
vector two = <0.5, 0.5, 2.2>;


components(one + two);
}
}
</source>


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
The above source will turn into a script that does not compile because  cpp replaces literally:
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
<source lang="lsl2">
variables: When you declare a function in LSL that takes parameters,
default
you need to declare what type the parameters are of. Macro parameters
{
don´t have a type. The preprocessor replaces stuff literally. You
touch_start(integer t)
could use a vector or some random string as a parameter with
{
"kAgentKey". Cpp would do the replacement just fine. It replaces
vector one = ZERO_VECTOR;
text like a search-and-replace operation you perform with your
vector two = <0.5, 0.5, 2.2>;
favourite editor does.
llSay(0, ((string)(one + two.x)) + ((string)(one + two.y)) + ((string)(one + two.z)));
}
}
</source>


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
[http://www.gnu.org/software/m4/ | m4].


"vAgentPos" and "fAgentDistance" are also macros and work the same way
Introduce another vector and it works great:
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>
<source lang="lsl2">
#include <lslstddef.h>
 
#define component(f)  ((string)(f))
#define components(v)  llSay(0, component(v.x) + component(v.y) + component(v.z))
 
 
default
{
event touch_start(int t)
{
vector one = ZERO_VECTOR;
vector two = <0.5, 0.5, 2.2>;
 
vector composed = one + two;
components(composed);
}
}
</source>
 
 
You can find this example in the [https://github.com/Ratany/lsl-repo git repository that accompanies this article] in "projects/components".
 
'''Macros are not functions.'''
 
 
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 preprocessing and to 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:
 
 
<source lang="lsl2">
#include <lslstddef.h>
#include <lslstddef.h>


Line 976: Line 704:
}
}
}
}
</lsl>
</source>
 


There isn´t much new to it.  "unless" is defined in "lslstddef.h" and speaks for itself.  Just make sure you know what you´re doing when you use "unless(A && B)":  Did you really want "A && B", or did you want "A || B"?  Think about it --- or perhaps keep it simple and don´t use conditions composed of multiple expressions with "unless".


There isn´t much new to it.  "unless" is defined in "lslstddef.h" and
"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.
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
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" evaluates to the greater value of the two parameters it is supplied with.
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
"vRandomDistance" is used to help with that the object shouldn´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.  Like many other things, "llSetKeyframedMotion()" uses offsets ...
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
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:
introduce another vector for the offset when you could simply do it in
one line like this:




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




That´s one thing all these macros are for, aren´t they?  Yes they are
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 probably 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
--- 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) <
"#define FMax(x, y) (((llFabs((x) >= (y))) * (x)) + ((llFabs((x) <
(y))) * (y)))" and another one for integers.
(y))) * (y)))" and another one for integers.
Line 1,036: Line 732:




<lsl>
<source lang="lsl2">
llSetKeyframedMotion([( (llList2Vector(llGetObjectDetails(llList2Key(agents, goto), [OBJECT_POS] ),
llSetKeyframedMotion([( (llList2Vector(llGetObjectDetails(llList2Key(agents, goto), [OBJECT_POS] ),
                                       0) + (<0.5, 0.5, 0.5>)) - (here) ),
                                       0) + (<0.5, 0.5, 0.5>)) - (here) ),
Line 1,042: Line 738:
                                   distance / 3.0) < (1.0 / 45.0) ) ) * (1.0 / 45.0) ) )],
                                   distance / 3.0) < (1.0 / 45.0) ) ) * (1.0 / 45.0) ) )],
                     [KFM_DATA, KFM_TRANSLATION]);
                     [KFM_DATA, KFM_TRANSLATION]);
</lsl>
</source>




Letting aside that the code would be totally different when written in
Letting aside that the code would be totally different when written in C, a C compiler would optimze it.  I don´t know what the LSL compiler does.
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
'''Always be aware of what your macros turn into.'''  The code from example-1_D turns into:
example-1_D turns into:




<lsl>
<source lang="lsl2">
distance /= 3.0;
distance /= 3.0;
vector offset = ( < (llFrand((3.5)) - llFrand((3.5))), (llFrand((3.5)) - llFrand((3.5))),
vector offset = ( < (llFrand((3.5)) - llFrand((3.5))), (llFrand((3.5)) - llFrand((3.5))),
Line 1,061: Line 754:
llSetKeyframedMotion([offset, ( ( (llFabs( ((distance)) >= (0.03) ) ) * ((distance)) ) + ( (
llSetKeyframedMotion([offset, ( ( (llFabs( ((distance)) >= (0.03) ) ) * ((distance)) ) + ( (
                                     llFabs( ((distance)) < (0.03) ) ) * (0.03) ) )], [KFM_DATA, KFM_TRANSLATION]);
                                     llFabs( ((distance)) < (0.03) ) ) * (0.03) ) )], [KFM_DATA, KFM_TRANSLATION]);
</lsl>
</source>




It´s still awful to read.  What did you expect?  The source looks nice
It´s still awful to read.  What did you expect?  The source looks nice and clear (not in the wiki but in emacs) 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.
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
'''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.
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
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.
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======
===Script memory and performance with cpp===


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




<lsl>
<source lang="lsl2">
#include <lslstddef.h>
#include <lslstddef.h>


Line 1,134: Line 808:
}
}
}
}
</lsl>
</source>




You can find this example in the
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".
[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
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.
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
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.
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
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.
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.'''
'''Test with your actual script what difference it makes.'''


It seems obvious that you would waste memory when you use a macro that
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.
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
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. Perhaps the LSL compiler is capable of inlining functions?
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
When programming in C or perl and you have a statement like




<lsl>
<source lang="lsl2">
if((A < 6) && (B < 20))
if((A < 6) && (B < 20))
[...]
[...]
</lsl>
</source>




Line 1,191: Line 839:




<lsl>
<source lang="lsl2">
if((A < 6) && (some_function(B) < 20))
if((A < 6) && (some_function(B) < 20))
[...]
[...]
</lsl>
</source>




LSL is stupid in that it will call the function or evaluate the
LSL is buggy 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:
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>
<source lang="lsl2">
integer some_function(integer B)
integer some_function(integer B)
{
{
Line 1,230: Line 871:
     }
     }
}
}
</lsl>
</source>
 
 
This example will yield a math error because some_function() is called even when it should not be called.  Such nonsense should be fixed.  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 diminish performance more than you otherwise would.
 
'''Avoid creating "if" statements with conditions composed in such a way that expressions are evaluated which should not be evaluated.'''
 
Imagine how much processing power could be saved if this bug was fixed.
 
====Inlining Functions====
 
[http://en.wikipedia.org/wiki/Inline_expansion Inlining] functions is possible with cpp.  Last night I looked at some code I´m working on and inlined a function.  With a line in the state_entry event telling me what llGetFreeMemory() returns, it said 30368 before the function was inlined and 31000 after the function was inlined: "Huh? Over 600 bytes saved?!"
 
632 bytes is almost 1% of the total amount of memory a script gets and about 2% of what this script has free.  The function doesn´t return anything and doesn´t take parameters.  It was a part of the code I had put into its own function to avoid convoluted code, and there is only one place the function is called from:  Those are ideal conditions to inline the function.
 
As suggested before, do your own detailed testing with "real" sources, i. e. not with something written for the purpose of testing. [http://en.wikipedia.org/wiki/Garbage_collection_(computer_science) Garbage collection] appears to happen when functions are returned from, and timing issues may be involved.  I wonder if there is garbage collection (triggered) every time a scope is exited.  This means that through inling a function, your script may need more memory during runtime than it otherwise would --- or not.
 
Since potentially saving 1% of memory just by inlining one function is a lot, I decided that this article should have a section about it.  It´s not only about saving memory:  You probably have noticed that functions in LSL scripts eat memory big time.  You may, out of necessity, have developed a bad style that avoids them as much as possible.
 
Please behold this --- rather artificial and intentionally not optimised --- example:
 
 
<source lang="lsl2">
#include <lslstddef.h>
 
#define Memsay(...)                llOwnerSay(fprintl(llGetFreeMemory(), "bytes free", __VA_ARGS__))
 
#define LIST                      numbers
#define iGetNumber(_n)            llList2Integer(LIST, (_n))
 
#define foreach(_l, _n, _do)      int _n = Len(_l); LoopDown(_n, _do);
 
#define iMany                      10
 
#define INLINE_FUNCTIONS          1
 
 
list LIST;
 
#if INLINE_FUNCTIONS
 
#define multiply_two(n) \
{ \
Memsay("multiply_two start"); \
foreach(LIST, two, opf(iGetNumber(n), "times", iGetNumber(two), "makes", iGetNumber(n) * iGetNumber(two))); \
Memsay("multiply_two end"); \
}
 
 
#define multiply_one() \
{ \
Memsay("multiply_one start"); \
foreach(LIST, one, multiply_two(one)); \
Memsay("multiply_one end"); \
}




This example will yield a math error because some_function() is called
#define handle_touch() \
even when it should not be called.  Such nonsense should be
{ \
fixed. Calling the function is an undesireable side effect.  To work
LIST = []; \
around it so that some_function() isn´t called, you need to add
int n = iMany; \
another "if" statement, which eats memory.
LoopDown(n, Enlist(LIST, (int)llFrand(2000.0))); \
multiply_one(); \
}


Depending on what the function or the macro does, your "if" statements
#else
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.'''
void multiply_two(const int n)
{
Memsay("multiply_two start");
foreach(LIST, two, opf(iGetNumber(n), "times", iGetNumber(two), "makes", iGetNumber(n) * iGetNumber(two)));
Memsay("multiply_two end");
}


======Use Parentheses======
 
void multiply_one()
{
Memsay("multiply_one start");
foreach(LIST, one, multiply_two(one));
Memsay("multiply_one end");
}
 
 
void handle_touch()
{
LIST = [];
int n = iMany;
LoopDown(n, Enlist(LIST, (int)llFrand(2000.0)));
multiply_one();
}
 
#endif
 
 
default
{
event touch_start(const int t)
{
Memsay("before multiplying");
handle_touch();
Memsay("after multiplying");
LIST = [];
Memsay("list emptied");
}
 
event state_entry()
{
Memsay("state entry");
}
}
</source>
 
 
When you make this with the functions ''not'' inlined, the output is like:
 
 
<source lang="bash">
[07:39:09]  Object: ( 50 kB ) ~> 55472 bytes free state entry
[07:39:15]  Object: ( 50 kB ) ~> 55472 bytes free before multiplying
[07:39:15]  Object: ( 49 kB ) ~> 55156 bytes free multiply_one start
[07:39:16]  Object: ( 49 kB ) ~> 54976 bytes free multiply_two start
[...]
[07:39:20]  Object: ( 49 kB ) ~> 54802 bytes free multiply_two end
[07:39:20]  Object: ( 49 kB ) ~> 54802 bytes free after multiplying
[07:39:20]  Object: ( 49 kB ) ~> 54802 bytes free multiply_one end
[07:39:20]  Object: ( 50 kB ) ~> 54802 bytes free list emptied
</source>
 
 
With the functions inlined, the numbers look a bit different:
 
 
<source lang="bash">
[07:39:41]  Object: ( 51 kB ) ~> 57008 bytes free state entry
[07:39:51]  Object: ( 51 kB ) ~> 57008 bytes free before multiplying
[07:39:51]  Object: ( 51 kB ) ~> 56696 bytes free multiply_one start
[07:39:51]  Object: ( 51 kB ) ~> 56696 bytes free multiply_two start
[...]
[07:39:56]  Object: ( 51 kB ) ~> 56510 bytes free multiply_two end
[07:39:56]  Object: ( 51 kB ) ~> 56510 bytes free multiply_one end
[07:39:56]  Object: ( 51 kB ) ~> 56510 bytes free after multiplying
[07:39:56]  Object: ( 51 kB ) ~> 56510 bytes free list emptied
</source>
 
 
This example has only three functions, and inlining them saves between 1536 (by state entry) and 1708 (after the script has run) bytes.  I don´t understand how these numbers come together when I look at  [[LSL_Script_Memory#Functions|this page]] --- unless declaring a function does "automatically take up a block of memory (512 bytes)".
 
As said before:  Test with your "real" sources.
 
'''When inlining functions, you need to make sure that the names of variables do not overlap.'''  Otherwise, you may generate scripts in which your code unexpectedly works with different variables when it is suddenly inlined.
 
The LSL compiler will not give you error messages about already declared variables when you keep the enclosing brackets ("{}") from the function definition with the define:  The opening bracket creates a new scope; the closing bracket closes the scope.  (Creating a new scope doesn´t appear to use memory by itself.  You could make use of that.)
 
This example keeps the brackets with the defines.  This is to show that you can convert functions into macros with minimal effort and to keep the code identical for the test.  The brackets from the functions are not needed with the defines.  You should remove them from the defines when you inline functions.  Removing them makes it less likely that overlapping variables go unnoticed.
 
You can find this example in the [https://github.com/Ratany/lsl-repo git repository that accompanies this article] in "projects/inline".
 
===Use Parentheses===


When you look at this simple macro
When you look at this simple macro




<cpp>
<source lang="cpp">
#define times3(x)                  x * 3
#define times3(x)                  x * 3
</cpp>
</source>




Line 1,261: Line 1,044:




<lsl>
<source lang="lsl2">
#define times3(x)                  x * 3
#define times3(x)                  x * 3
[...]
[...]
Line 1,269: Line 1,052:
llSay(0, (string)b);
llSay(0, (string)b);
[...]
[...]
</lsl>
</source>




Line 1,275: Line 1,058:




<lsl>
<source lang="lsl2">
#define times3(x)                  x * 3
#define times3(x)                  x * 3
[...]
[...]
Line 1,283: Line 1,066:
llSay(0, times3(a - b));
llSay(0, times3(a - b));
[...]
[...]
</lsl>
</source>




Line 1,295: Line 1,078:




<cpp>
<source lang="cpp">
#define times3(x)                  (x) * 3
#define times3(x)                  (x) * 3
</cpp>
</source>




Line 1,306: Line 1,089:




<lsl>
<source lang="lsl2">
#define plus3times3(x)            (x) * 3 + 3
#define plus3times3(x)            (x) * 3 + 3
[...]
[...]
Line 1,314: Line 1,097:
int d = 15;
int d = 15;
llSay(0, (string)(plus3times3(a - b) * plus3times3(c - d));
llSay(0, (string)(plus3times3(a - b) * plus3times3(c - d));
</lsl>
</source>




Line 1,320: Line 1,103:




Imagine you defined 'plus3times3' without any parentheses 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 weird results.
waste hours trying to figure out why your script yields incredibly
weird results.


To get it right:
To get it right:




<lsl>
<source lang="lsl2">
#define plus3times3(x)            ((x) * 3 + 3)
#define plus3times3(x)            ((x) * 3 + 3)
[...]
[...]
Line 1,335: Line 1,116:
int d = 15;
int d = 15;
llSay(0, (string)(times3(a - b) * times3(c - d));
llSay(0, (string)(times3(a - b) * times3(c - d));
</lsl>
</source>




Line 1,341: Line 1,122:




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




<lsl>
<source lang="lsl2">
if(A || B && C || D > 500)
if(A || B && C || D > 500)
[...]
[...]
</lsl>
</source>




Line 1,354: Line 1,134:




<lsl>
<source lang="lsl2">
if((A || B) && (C || (D > 500)))
if((A || B) && (C || (D > 500)))
[...]
[...]
</lsl>
</source>




That is not ambiguous.  Since "&&" usually takes precedence over "||",
That is not ambiguous.  Since "&&" usually takes precedence over "||", it´s different from the version without parentheses.  It probably doesn´t matter with LSL for the bug mentioned above, but I´m not even trying to figure it out. It does matter in C and perl at least 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.
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
Save yourself the hassle and use parentheses.  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.
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=====
===How to use cpp===


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




Line 1,384: Line 1,152:




The LSL compiler is one of the programs that would be confused by line
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
markers.  You can see what the line markers look like when you
preprocess one of your scripts with




<lsl>
<source lang="lsl2">
cpp your_script.lsl
cpp your_script.lsl
</lsl>
</source>




By default, cpp writes its output to stdout, i. e. it appears on your
By default, cpp writes its output to stdout, i. e. it appears on your screen.  Since it isn´t necessarily useful to have your LSL scripts appear on your screen like that, you may want to redirect the output into a file:
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>
<source lang="lsl2">
cpp -P your_script.lsl > your_script.i
cpp -P your_script.lsl > your_script.i
</lsl>
</source>




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


It´s that simple.
It´s that simple.


You can now write a script and use defines and macros and preprocess
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.  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
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 seperate directory, perhaps with other headers you frequently use.  You don´t want to specify in which directory you keep your "lslstddef.h" or other headers in all your sources:  The directory might change.
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
Cpp has another parameter for this: '-I'.  '-I' tells cpp where to look for files you include with something like '#include <lslstddef.h>'.
look for files you include with something like '#include <lslstddef.h>'.


If you haven´t pulled (i. e. downloaded) the
The difference between '#include "lslstddef.h"' and "#include <lslstddef.h>" is that '#include "lslstddef.h"' will make cpp look for "lslstddef.h" in the current directory (where your source is) while "#include <lslstddef.h>" makes it look in default directories. Such directories can be specified with '-I'.
[https://github.com/Ratany/lsl-repo git repository that accompanies this article],
please do so now.


When you look into the "projects" directory from the repository,
If you haven´t pulled (i. e. downloaded) the [https://github.com/Ratany/lsl-repo git repository that accompanies this article], please do so now.
you´ll find a few directories in it:


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


<lsl>
 
projects
<source lang="bash">
.
|-- bin
|-- bin
|-- clean-lsl.sed -> make/clean-lsl.sed
|-- components
|-- example-1
|-- example-1
|-- include
|-- include
|-- lib
|-- lib
|-- make
|-- memtest
|-- rfedip-reference-implementation
|-- rfedip-reference-implementation
`-- template-directories
|-- template-directories
</lsl>
`-- your-script
</source>




The "bin" directory contains tools; "include" is for files like
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.
"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
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".
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
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 to put them into the general "projects/lib" or "projects/include" directory.
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
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:
"lslstddef.h".  Since you´re starting a new project, you first copy
the template directories:




<lsl>
<source lang="bash">
[~/src/lsl-repo/projects] cp -avx template-directories/ your-script
[~/src/lsl-repo/projects] cp -avx template-directories/ your-script
‘template-directories/’ -> ‘your-script’
‘template-directories/’ -> ‘your-script’
Line 1,482: Line 1,218:
‘template-directories/lib’ -> ‘your-script/lib’
‘template-directories/lib’ -> ‘your-script/lib’
‘template-directories/src’ -> ‘your-script/src’
‘template-directories/src’ -> ‘your-script/src’
‘template-directories/tmp’ -> ‘your-script/tmp’
‘template-directories/dep’ -> ‘your-script/dep’
‘template-directories/bak’ -> ‘your-script/bak’
‘template-directories/Makefile’ -> ‘your-script/Makefile’
‘template-directories/Makefile’ -> ‘your-script/Makefile’
[~/src/lsl-repo/projects]
[~/src/lsl-repo/projects]
</lsl>
</source>
 


Make sure that all files and directories from the "template-directories" are copied.  When something is missing, the build system may not work.


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




<lsl>
<source lang="lsl2">
#include <lslstddef.h>
#include <lslstddef.h>


Line 1,504: Line 1,240:
}
}
}
}
</lsl>
</source>




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




<lsl>
<source lang="bash">
[~/src/lsl-repo/projects/your-script/src] cpp -P your_script.lsl > your_script.i
[~/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
your_script.lsl:1:23: fatal error: lslstddef.h: No such file or directory
Line 1,518: Line 1,253:
compilation terminated.
compilation terminated.
[~/src/lsl-repo/projects/your-script/src]
[~/src/lsl-repo/projects/your-script/src]
</lsl>
</source>




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




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




Using '-I', you tell cpp to look for files that are to be included in
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".
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:
You can now look at the outcome:




<lsl>
<source lang="lsl2">
[~/src/lsl-repo/projects/your-script/src] cat your_script.i
[~/src/lsl-repo/projects/your-script/src] cat your_script.i
default
default
Line 1,549: Line 1,280:
}
}
[~/src/lsl-repo/projects/your-script/src]
[~/src/lsl-repo/projects/your-script/src]
</lsl>
</source>




You may think that this is inconvenient. You do not want to type
So much effort to finally create such a little LSL script? No --- the effort here lies in giving you a reference which is supposed to help you to better understand the build system.
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 thatThere is a tool that can do
The effort to create an LSL script is no more than copying the "template-directories", saving your source in the "src" directory of the copy and running 'make'You got a build system that does everything else for you when you run 'make'.
it for you.  It´s called "make".


====Make====
==Make==


Very nicely said, [http://www.gnu.org/software/make/ Make] "is a tool
Very nicely said, [http://www.gnu.org/software/make/ Make] "is a tool which controls the generation of executables and other non-source files of a program from the program's source files."
which controls the generation of executables and other non-source
files of a program from the program's source files."


Make uses 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.


As you may have noticed, in the previous section of this article I
Suppose you have written a source "example.lsl".  It includes "lslstddef.h" and uses a function defined in "getlinknumbers.lsl".  Without Make, you´d have to run cpp to preprocess your source and use sed to clean it up.  Then you´d have to make an entry in the look-up table so you can have the script replaced automaticallyIf you want to see the debugging version of the script, you´d use astyle to format it nicelySince you want to use the compressed version of the script, you´d use i2o.pl to create the compressed versionFinally, you modify the source to fix a bug you found when testing the script.  Except for making an entry in the look-up table, you´d have to do all the steps again.
have referred to "scripts" (LSL scripts) and "sources", without
mentioning what the difference isThe source is what you writeIt
can be an LSL script that can be compiled by an LSL compiler right
awayThat´s probably what you have written until you read this
article.


After reading this article, you may not want to write plain LSL
Suppose you modify "lslstddef.h" or "getlinknumbers.lsl"You´d have to remember that you did this and that you need to process your source againYou might have several sources because the object you´re working on has several scriptsSome of them do not include "getlinknumbers.lsl". Which ones do you need to process again because they include a file you have modified, and which ones not?
scripts anymoreYou may want to use a decent editor, a preprocessor
and MakeWhat 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 firstThat´s what I was trying to
refer to as "source".


You could say that
Make figures out from the Makefile how to process your sourcesIt also figures out which source files do need to be processed again and which ones don´t when files the sources include have been modifiedInstead of running a bunch of commands, you simply run 'make'.
'''An LSL script is the output of a preprocessor that processes a source file.'''
I don´t write LSL scripts anymoreI write source files that can be
turned into LSL scriptsIt´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
You do not need to understand Make or Makefiles to make creating LSL scripts easierYou find a suitable Makefile in the [https://github.com/Ratany/lsl-repo git repository that accompanies this article]. The Makefile is "projects/make/Makefile".
files"Make isn´t used here to generate an
[http://en.wikipedia.org/wiki/Executable | executable]. LSL scripts
are not exectutable, and apparently the LSL compiler compiles them
into [http://en.wikipedia.org/wiki/Bytecode | byte code].


"Other non-source files" can also mean documentation.  The provided
This Makefile is symlinked into the template-directoriesThere is only one Makefile for all your projectsIt works for all of themThis has the advantage --- or disadvantage --- that when you change the Makefile, the changes apply to all your projects.
makefile has entries that use pdflatex to create a PDF file from a
[http://www.latex-project.org/ LaTeX]
source and to copy the PDF into the project directoryI don´t
write PDF filesI write source files that can be turned into PDF
filesIt´s much easier to create PDF files that way than it is to
write them :)


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.


Preprocessing "your-script.lsl" like in the previous section generates
===How to use Make===
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
Enter the directory of the project you are working on and run 'make'. That´s allThe section "Repository" has some details.
that is Make, and a so-called MakefileIt´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
You can run make from within emacs: M-x compileEmacs 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.
scripts easierYou find a suitable Makefile in the
[https://github.com/Ratany/lsl-repo git repository that accompanies this article].
The Makefile is "projects/make/Makefile".


This Makefile is symlinked into the template-directories.  There is
===Make "your-script.lsl"===
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
Assume you want to try out the "your-script.lsl" source from above: Enter the directory for the project ("/projects/your-script/") and run 'make' --- or load the source into emacs and run make from within emacs. I´ll do it with emacs:
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=====
C-x-f ~/src/lsl-repo/projects/your-script/src/your-script.lsl


Enter the directory of the project you are working on and run 'make'.
M-x compile
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"=====
Emacs now asks you for the command to use and suggests 'make -k'.  This won´t work because Make needs to be run from within one directory above, i. e. "~/src/lsl-repo/projects/your-script/" rather than "~/src/lsl-repo/projects/your-script/src".  So instead of 'make -k', you need to use 'cd ..; make -k'.  Just edit what emacs suggests, then press Enter.


Assume you want to try out the "your-script.lsl" source from
Emacs will check if it has any files that haven´t been saved yet because you might have forgotten to save your source. If there are files to save, it will ask you whether to save them or notThen emacs will run make and open a new buffer to show you the output of 'make':
above. You need to make an LSL script which you can put into an object
for thisSuppose you make a box and name it "testbox".


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


<source lang="bash">
-*- mode: compilation; default-directory: "~/src/lsl-repo/projects/your-script/" -*-
Compilation started at Sat Jan 11 15:21:51


<lsl>
cd ..; make -k
[~/src/lsl-repo/projects/your-script] make
generating dependencies: dep/your-script.di
COMPILING src/your-script.lsl
generating dependencies: dep/your-script.d
preprocessing src/your-script.lsl
   8 153 dbg/your-script.i.clean
   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
Compilation finished at Sat Jan 11 15:21:52
tells you that it was compling --- you could say preprocessing instead
</source>
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:


When you need to process your script again, you can also do this within emacs: M-x recompile.  I have bound the function to C-f1, so I´d just press Ctrl+f1.  For an example of how to do such a keybinding, please see "emacs/emacs" in the [https://github.com/Ratany/lsl-repo git repository that accompanies this article].


# "projects/your-script/dbg/your-script.i" and
To try out the script you just created in sl, you can switch to your sl client and make a box.  Make a new script in the box and open the new script.  In the built-in script editor, type into the first line:


# "projects/your-script/bin/your-script.o".


<source lang="lsl2">
// =your-script.o
</source>


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
Now click on the "Edit" button of the built-in script editor.  Provided that you successfully prepared the automatic replacement of your scripts as described above, the compressed version of your script appears in the built-in script editor.  It will be compiled by the sl server and run --- or you will see an error message in the built-in script editor.
scripts in the built-in editor


==astyle==


With the Makefile from the repository, Make does a bit more than only
Astyle is a nice tool to format your code so that it´s easier to readSince you write your sources so that they are easy to read anyway, you may not want to use it to reformat your sources --- unless some lines are so long that they don´t fit very well on wiki pages maybe.
running cppIt 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:


However, the output of cpp isn´t too readable.  Therefore, the build system uses astyle to reformat it when it generates debugging versions of the scripts (in dbg/*.i).  This is entirely optional.


<lsl>
==sed==
[~/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>


Sed is used by the build system for a number of things.  One of these things is what you might call postprocessing:  Preprocessing your sources can leave artifacts in the output of cpp due to the way macros (see "lslstddef.h") are used.


Make runs cpp, [http://www.gnu.org/software/sed/ sed],
Sed is used to remove these artifacts, i. e. lines like "};" and ";". A closing bracket doesn´t need to be followed by a semicolon, and lines empty except for a semicolon are obsolete. Postprocessing with sed replaces "};" which "}" and removes empty lines.
[http://en.wikipedia.org/wiki/Wc_(Unix) wc],
[http://astyle.sourceforge.net/ astyle], etags (which comes with
emacs), [http://en.wikipedia.org/wiki/Cat_(Unix) cat],
[http://www.gnu.org/software/coreutils/ echo] and
[http://www.gnu.org/software/coreutils/ md5sum].


It also runs i2o.pl, which is a perl script.  You can find it in the
The rules used by sed for this are in a so-called script which you can find as "projects/make/clean-lsl.sed" in the [https://github.com/Ratany/lsl-repo git repository that accompanies this article].
[https://github.com/Ratany/lsl-repo git repository that accompanies this article]
in the projects/bin directory.


i2o.pl comes from the [http://opengate.ma8p.com/open9.tar.gz source]
==i2o.pl==
of the [http://ma8p.com/~opengate/ 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
The perl script i2o.pl comes from the [http://opengate.ma8p.com/open9.tar.gz source] of the [http://ma8p.com/~opengate/ Open Stargate Network] stargates.
'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'
It "compresses" your scripts by removing whitespace and by shortening the names of variables. Since variables are renamed, the compression cannot be undone entirely (like with astyle)Perhaps "shrinking" is a better word for this than "compressing".
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
The point of shrinking your scripts is saving memoryYou cannot save (i. e. upload) a script (or contents of a notecard) that takes more than 64kB: The script (or the notecard) gets truncated, probably by the sl serverCpp already removes comments and blank lines from the source, and i2o.pl shrinks the script further.
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:


Side effects of shrinking are that sometimes a script appears to need less script memory to run, and shrunken scripts appear to run a little faster.  I haven´t really tested either, and both doesn´t seem to make sense because the scripts are compiled to byte-code anyway.  Test it if you like; it would be interesting to see the results.  I always use the shrunken version unless I need to look at the debugging version, and it works great.


<cpp>
==git==
#if DEBUG0
#define DEBUGmsg0(...)                  DEBUGmsg(__VA_ARGS__)
#else
#define DEBUGmsg0(...)
#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>
#include <lslstddef.h>
// [...]
int a = Len(lChains);
DEBUGmsg0("there are", a / iSTRIDE_lChains, "chains");
// [...]
</lsl>


Git isn´t really involved here, other than that the build system and examples described and used in this article --- and the article itself --- is available as a [git://dawn.adminart.net/lsl-repo.git git repository] for your and my convenience.  It´s convenient for you because you can easily download it.


The output of cpp, when 'DEBUG0' is not true, looks like this:
It´s convenient for me because git makes it easy to keep the repository up to date.  If several people would contribute to what is in the repository, it would be convenient for them as well because git makes it easy to put together the work done by many people on the same thing.


It can be convenient --- or useful --- for you because it makes it easy to keep track of changes.  When you create a git repository from or for the sources of a project you are working on, you can not only apply (commit) every change to the repository accompanied by an entry in a log which helps you to figure out what you have done.


<lsl>
When you make a commit, it is a good idea to make a commit for one particular thing or objective, like "fixed the bug with too many numbers". It is irrelevant how many changes you made or how many files you changed to achieve the objective. Git is not about what happened to individual files but about what happened to the project. Still you can track every change to every single file and let git show you exactly what has changed.
  integer a = llGetListLength(lChains);
  ;
</lsl>


You can also undo changes by going back to previous commits and experiment with variations (by creating so-called branches).  Git works for this much better than making backups or copies of your sources.  With backups or copies, it doesn´t take long before I can´t remember what a backup or copy was about and what has changed and whether the copy has become obsolete or not.


The debug message disappears because it´s defined to disappear.  But
These features can make it worthwile to use git even when you don´t share your sources or don´t have several people working on the same projectThere is lots of documentation about git, and one place you may want to check out is the [https://git-scm.com/doc documentation].
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´tIf 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
Emacs supports git out of the box, see [http://alexott.net/en/writings/emacs-vcs/EmacsGit.html here] and [http://www.emacswiki.org/emacs/Git there].
can find this sed-script as "projects/clean-lsl.sed".


Wc is a nice tool to give you statistics about text files.  It can
Using CVSs like git used to be awful even when you merely wanted to get a copy: There were many different ones that used slightly different commands to download a copy of the repository --- and yet different commands when you wanted to update a copy, perhaps a year later, you already had.  You could either spend half an hour or so to figure out how to update your existing copy or download everything again, which might also take half an hour or so.
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
Git works much better.  It seems that a lot of repositories have been migrated to gitDon´t let your perhaps frustrating experiences with CVSs keep you from giving git a try.
that it´s easier to read than the output of cppIt´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
==References==
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.


* The [https://github.com/Ratany/lsl-repo git repository that accompanies this article] contains the sources of reference implementations of devices which are compatible with the [[LSL_Protocol/rfedip|Ratany fine Engineering Device Interface Protocol]].  Please see "projects/rfedip-reference-implementation".


* [[LSL_Alternate_Editors|Alternate editors]] and build systems/IDEs.


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

Latest revision as of 11:19, 30 December 2015

How to make creating LSL scripts easier

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

One of these tools is a preprocessor called cpp. It comes with GCC. Among the tools mentioned in this article, cpp is the tool which can make the biggest difference to how you write your sources and to how they look.


The idea of using cpp comes from the source of the Open Stargate Network stargates.

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.


This article is accompanied by a repository. It is recommended that you pull (download) the repository. The repository contains some example sources used with this article, a few perl scripts and a Makefile. They are organised in a directory structure which they are designed to work with. You will have your own build system for LSL scripts when you have pulled this repository.

The repository does not contain basic standard tools like perl, cpp, make or an editor. This article does not attempt to tell you how to install these. You know better how to install software on your computer than I do, and how you install software also depends on which operating system you are using.

I´m using Linux, currently Gentoo.

Documentation is plentiful. You may find different software that does the same or similar things and may work better for you or not.

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.

Repository

It´s probably more fun to be able to try out things yourself while reading this article. If you want to do so, pull the repository. You can run 'git clone git://dawn.adminart.net/lsl-repo.git' to get it.

Once cloned, you have a directory called "lsl-repo". Inside this directory, you have another one called "projects".

You can put the "projects" directory anywhere you like, and you can give it a different name if you want to.

Inside the "projects" directory, you have more directories like this:


.
|-- bin
|-- clean-lsl.sed -> make/clean-lsl.sed
|-- example-1
|-- include
|-- lib
|-- make
|-- memtest
|-- rfedip-reference-implementation
|-- template-directories
`-- your-script


When you rename or move "bin", "include", "lib", "make" or "clean-lsl.sed", your build system will not work anymore (unless you edit some files and make adjustments). Don´t rename or move them.

Do not use names for directories or files that contain special characters or whitespace. Special characters may work, whitespace probably doesn´t. Maybe it works fine; I haven´t tested.

Suppose you want to try out "example-1". Enter the directory "example-1" and run 'make':


[~/src/lsl-repo/projects/example-1] make
generating dependencies: dep/example-1.di
generating dependencies: dep/example-1.d
preprocessing src/example-1.lsl
  39 1202 dbg/example-1.i.clean
[~/src/lsl-repo/projects/example-1]


You will find the LSL script as "projects/example-1/bin/example-1.o". A debugging version is "projects/example-1/dbg/example-1.i". When you modify the source ("projects/example-1/src/example-1.lsl") just run make again after saving it, and new versions of the script will be created that replace the old ones.

Some files are generated in the "dep" directory. They are needed by the build system to figure out when a script needs to be rebuilt and whether entries need to be added to a look-up table or not. You can ignore those. A file is generated in the "make" directory. The file is needed to automatically replace scripts with the script editor built into your sl client, which will be described later.

Now you can log into sl with your sl client. Make a box, create a "New Script" in the box and open the "New Script". It shows up in the built-in editor. Delete the script in the built-in editor and copy-and-paste the contents of "projects/example-1/bin/example-1.o" into the built-in editor. Then click on the "Save" button of the built-in editor. (There is a way to do this kind of script-replacement automatically. It will be explained later.)

The "template-directories" is what you copy when you want to write your own source. Copy it with all the files and directories that are in it. Suppose you make a copy of the "template-directories" and name the copy "my-test". You would then put your own source into "my-test/src", like "my-test/src/my-test.lsl".

Of course, you can have several source files in "my-test/src". For all of them, scripts will be created. The "my-test" directory is the project directory for your project "my-test". When you are working on an object that requires several scripts, you´ll probably want to keep them all together in the project directory you made for this object.


If you get an error message when you run 'make', you are probably missing some of the tools the build system uses. Install the missing tools and try again.

Tools you need for the build system

Required tools:


  • editor: your favourite editor, Emacs is recommended


Optional tools:



You can replace "make/Makefile" with "make/Makefile-no-optional" and try without the optional tools. You can make "echo" optional, but automatically replacing scripts won´t work unless you can find a replacement for "@echo "// =`basename $@`" > $@" in the Makefile. Since perl is required, you could replace "echo" (and "basename" and "sed") with perl scripts. You can probaly make further modifications so you won´t need perl, but what do you do without it?


When you´re messing with Windoze, you may want to check out Cygwin. When you´re stuck with Apple, maybe this repository helps.

However, under those conditions, you might be better off with a minimal Debian installation in a VM and installing needed packages from there. Last time I tried, Debian still let you install a minimal system and didn´t force you to use a GUI version of the installer. Fedoras´ installer sucks, and with archlinux, you need to know what you´re doing.

In case you want to upgrade from either Macos or Windoze, Fedora is a good choice.

Editor

To create scripts, you do need an editor to write your sources. 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.

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 use a build system.
  • You can share your sources and work together with others because you can use tools like git and websites which will host your repositories for free. Even when you don´t share your sources, git is a very useful tool to keep track of the changes you're making.
  • Your window manager handles the windows of your editor so that you are not limited to the window of your sl client which doesn´t do a great job when it comes to managing windows. You are not limited to a single monitor, and 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, even your favourite one :)


You can use any editor you like. An editor is essential and one of your most important tools. When your editor sucks, you cannot be productive. Use the best editor you can find.

Emacs

Emacs is my favourite editor. Give it a try, and don´t expect to learn everything about it within a day. It´s enough when you can move the cursor around, type something and load and save files. Over time, you will learn more just by looking up how to do something and trying it. You will find your way of doing what you need and adjust emacs to what you want --- or you don´t and use it as it is. Lots of documentation is available.

By default, the cursor keys move the cursor. Ctrl+xf loads a file into a new buffer and Ctrl+xs saves it. Ctrl+xb lets you switch to another buffer. These are usually written as C-x-f, C-x-s and C-x-b.

Emacs is worthwhile to learn. It´s the only editor you need to learn because you don´t need any others. It has been around since a long time. If you had learned it twenty-five years ago, that would have saved you the time wasted with other editors which nowadays may not even exist anymore.

It doesn´t hurt to give it a serious try.

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.

Syntax highlighting in emacs with the lsl-mode from the repo

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) to give you better syntax highlighting with it.

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.

The build system creates a so-called tags-file for you. Emacs can read it automatically. Esc+. (M-.) lets you enter a tag you want to go to and takes you there.

Git

See this page: A number of version control systems are supported, and git is one of them. Additional packages are available for this --- I haven´t tried any of them yet.

Customizing Emacs

The git repository that accompanies this article has a directory "emacs". It is recommended that you copy this directory to a suitable location like your home directory.

Two files are in this directory, "emacs" and "lsl-mode.el". "lsl-mode.el" provides a syntax highlighting mode for emacs. When you start emacs, it loads a file, usually "~/.emacs", and evaluates its contents. You could say it´s a configuration file.

That´s what the file "emacs" in the "emacs" directory is. You can move this file to "~/.emacs" or take a look at it and add what you need from it to the "~/.emacs" file you may already have when you are already using emacs.

"~/.emacs" contains instructions that make emacs load a file called "~/emacs/lsl-mode.el". You compile this file from within emacs with M-x byte-compile-file. Compiling it creates "~/emacs/lsl-mode.elc". Compiling "~/emacs/lsl-mode.el" is recommended, and once you have compiled it, you need to edit "~/.emacs" and change "(load "~/emacs/lsl-mode.el")" to "(load "~/emacs/lsl-mode.elc")".

Without using lsl-mode.el (or lsl-mode.elc), syntax highlighting for your sources when you create LSL scripts won´t be as nice, so you really want to do this.

Line Numbers

Line numbers have become an anachronism. They were used to create what was called "spaghetti code" with statements like "if(A) then goto 100;". Seriously. There was no other way. It helped to get you to think straight as a programmer. Events are horrible when you think straight, and I don´t like them. They are worse than Makefiles. Seriously. But there is no other way.

The built-in script editor displays line numbers. Emacs can do that, too. There´s a mode for it, called linum-mode. Try M-x linum-mode. After all, line numbers can be very useful for reference.

When you need to add line numbers to the contents of a buffer, look here.

Other Editors

In case you find that you don´t get along with emacs, you may want to check out this page about editors. There is also a page about alternate editors and build systems/IDEs.

Try vi, or vim, instead. You don´t need to try any other editors. Your time is better spent on learning emacs or vi.

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.

Manually Replacing Scripts

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.

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 loose 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 (or a magnifying glass with a non-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.

Automatically Replacing Scripts

A program that replaces the script needs to know which script must be replaced with which file. To let the program know this, a useful comment is put into the script, and a look-up table is used. The comment tells the program the name of the script. The look-up table holds the information which script is to be replaced with which file. The build system puts the comment into the script and creates entries in the look-up table. You just click the "Edit" button to replace the script.

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.

You´ll find this script in the git repository that accompanies this article as "projects/bin/replace.pl". It works like this:


  • 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 both the file and the look-up table
  • look up the key in the look-up table
  • 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 both the file and the look-up table


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)
  • change the debug setting "ExternalEditor" so that your sl client starts the perl script instead of your favourite editor
  • make sure that your LSL script starts with a line like "// =example-script.o" --- the build system does this for you
  • make sure that the look-up table exists --- the build system does this for you


To modify the perl script to use your favourite editor, edit it and change "use constant EDITOR => "emacsclient";" --- and perhaps "use constant EDITORPARAM => "-c";". There are comments in the script explaining this.

When you have your sl client start the perl script, it is difficult or impossible to see error messages you may get when running the perl script. If there is an error, it will probably seem as if nothing happens when you click on the "Edit" button.

One way to test if it works is to make sure that the look-up table does not exist and then to click on the "Edit" button of the built-in script editor in your sl client. If the look-up table exists, you find the table as "projects/make/replaceassignments.txt". When your favourite editor starts to let you edit both the file your sl client saved and the (empty) look-up table, it is very likely that it´s working.

The build system fills the look-up table for you. It adds entries for new scripts as needed when you run 'make'. It does not delete entries from the table. To make such entries, the perl script "projects/bin/addtotable.pl" in the git repository that accompanies this article is used.

The first entry in the table that matches is used. If you plan to have multiple scripts with the same name, you may need to adjust addtotable.pl, replace.pl and the Makefile.

The build system 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" version 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.

A Preprocessor: cpp

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 an 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:


#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
	}
}


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:


#define event


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:


#ifdef event
#define test 1
#endif


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". With conditionals like "#ifdef", "#if" or "#ifndef", you can have code from your source put into the script depending on what is defined. For example, you can disable a block of code simly by putting "#if 0" at the start of the block and "#endif" at the end of the block. That´s easier than commenting out the whole region.

The library files in "projects/lib" in the git repository that accompanies this article make use of conditionals. The whole library file is included into the source, and defines are used to decide which functions from the libraries will be in the resulting script. You can see in "projects/rfedip-reference-implementation/src/rfedipdev-enchain.lsl" how it´s done.


The example script needs to set the physics type and to check if it was the owner of the object who touched it. It needs to know the position of the object the script is in and what agents are around. Add all this to the source, which is now example-1_B:


#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]);
	}
}


With


#define event
#define SLPPF                           llSetLinkPrimitiveParamsFast


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


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


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


float d = llVecDist(llList2Vector(llGetObjectDetails(llList2Key(agents, 0), [OBJECT_POS] ), 0),
          llList2Vector(llGetObjectDetails(llList2Key(agents, 1), [OBJECT_POS] ), 0));


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. You can use defines and macros to avoid this. If you were to compute the distance between two agents, you could use defines like:


#include <lslstddef.h>

#define kAgentKey(x)       llList2Key(agents, x)
#define fAgentsDist(a, b)  llVecDist(RemotePos(a), RemotePos(b))


Instead of the lengthy expression above, your source would then be:


float d = fAgentsDist(0, 1);


How much easier to write and read and maintain is that? How much less prone to errors is it?

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". They do not belong indented.

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:


#include <lslstddef.h>


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

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

#define iSTRIDE_agents     1
#define kAgentKey(x)       llList2Key(agents, x)
#define vAgentPos(x)       RemotePos(kAgentKey(x))
#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]);
	}
}


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 git repository that accompanies this article for a source that uses a strided list.

"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 parameters of functions: 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 and not give you error messages. It replaces text like a search-and-replace operation you perform with your favourite editor does. This can sometimes be limiting:


#include <lslstddef.h>

#define component(f)   ((string)(f))
#define components(v)  llSay(0, component(v.x) + component(v.y) + component(v.z))


default
{
	event touch_start(int t)
	{
		vector one = ZERO_VECTOR;
		vector two = <0.5, 0.5, 2.2>;

		components(one + two);
	}
}


The above source will turn into a script that does not compile because cpp replaces literally:


default
{
	touch_start(integer t)
	{
		vector one = ZERO_VECTOR;
		vector two = <0.5, 0.5, 2.2>;
		llSay(0, ((string)(one + two.x)) + ((string)(one + two.y)) + ((string)(one + two.z)));
	}
}


Introduce another vector and it works great:


#include <lslstddef.h>

#define component(f)   ((string)(f))
#define components(v)  llSay(0, component(v.x) + component(v.y) + component(v.z))


default
{
	event touch_start(int t)
	{
		vector one = ZERO_VECTOR;
		vector two = <0.5, 0.5, 2.2>;

		vector composed = one + two;
		components(composed);
	}
}


You can find this example in the git repository that accompanies this article in "projects/components".

Macros are not functions.


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 preprocessing 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:


#include <lslstddef.h>


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

#define fMetersPerSecond           3.0
#define fSecondsForMeters(d)       FMax((d), 0.03)

#define fRANGE(_f)                 (_f)
#define vMINRANGE(_f)              (<_f, _f, _f>)
#define fRandom(_fr)               (llFrand(fRANGE(_fr)) - llFrand(fRANGE(_fr)))
#define vRandomDistance(_fr, _fm)  (<fRandom(_fr), fRandom(_fr), fRandom(_fr)> + vMINRANGE(_fm))

#define fDistRange                 3.5
#define fMinDist                   0.5


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

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

#define iSTRIDE_agents     1
#define kAgentKey(x)       llList2Key(agents, x)
#define vAgentPos(x)       RemotePos(kAgentKey(x))
#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]);
			}

#undef AgentKey
#undef AgentPos
#undef AgentDistance

	}

	event state_entry()
	{
		SLPPF(LINK_THIS, [PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_CONVEX]);
	}
}


There isn´t much new to it. "unless" is defined in "lslstddef.h" and speaks for itself. Just make sure you know what you´re doing when you use "unless(A && B)": Did you really want "A && B", or did you want "A || B"? Think about it --- or perhaps keep it simple and don´t use conditions composed of multiple expressions with "unless".

"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" evaluates to the greater value of the two parameters it is supplied with.

"vRandomDistance" is used to help with that the object shouldn´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. Like many other things, "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:


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


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 probably 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:


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]);


Letting aside that the code would be totally different when written in C, a C compiler would optimze it. 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:


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]);


It´s still awful to read. What did you expect? The source looks nice and clear (not in the wiki but in emacs) 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:


#include <lslstddef.h>


#define WITH_DEFINE_FLOAT 0
#define WITH_DEFINE_INT   0


#if WITH_DEFINE_FLOAT
#define ftest 1.0
#else
float ftest = 1.0;
#endif

#if WITH_DEFINE_INT
#define itest 1
#else
int itest = 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("---");
	}
}


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.

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. Perhaps the LSL compiler is capable of inlining functions?

When programming in C or perl and you have a statement like


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


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:


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


LSL is buggy 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:


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");
        }
    }
}


This example will yield a math error because some_function() is called even when it should not be called. Such nonsense should be fixed. 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 diminish performance more than you otherwise would.

Avoid creating "if" statements with conditions composed in such a way that expressions are evaluated which should not be evaluated.

Imagine how much processing power could be saved if this bug was fixed.

Inlining Functions

Inlining functions is possible with cpp. Last night I looked at some code I´m working on and inlined a function. With a line in the state_entry event telling me what llGetFreeMemory() returns, it said 30368 before the function was inlined and 31000 after the function was inlined: "Huh? Over 600 bytes saved?!"

632 bytes is almost 1% of the total amount of memory a script gets and about 2% of what this script has free. The function doesn´t return anything and doesn´t take parameters. It was a part of the code I had put into its own function to avoid convoluted code, and there is only one place the function is called from: Those are ideal conditions to inline the function.

As suggested before, do your own detailed testing with "real" sources, i. e. not with something written for the purpose of testing. Garbage collection appears to happen when functions are returned from, and timing issues may be involved. I wonder if there is garbage collection (triggered) every time a scope is exited. This means that through inling a function, your script may need more memory during runtime than it otherwise would --- or not.

Since potentially saving 1% of memory just by inlining one function is a lot, I decided that this article should have a section about it. It´s not only about saving memory: You probably have noticed that functions in LSL scripts eat memory big time. You may, out of necessity, have developed a bad style that avoids them as much as possible.

Please behold this --- rather artificial and intentionally not optimised --- example:


#include <lslstddef.h>

#define Memsay(...)                llOwnerSay(fprintl(llGetFreeMemory(), "bytes free", __VA_ARGS__))

#define LIST                       numbers
#define iGetNumber(_n)             llList2Integer(LIST, (_n))

#define foreach(_l, _n, _do)       int _n = Len(_l); LoopDown(_n, _do);

#define iMany                      10

#define INLINE_FUNCTIONS           1


list LIST;

#if INLINE_FUNCTIONS

#define multiply_two(n)							\
	{								\
		Memsay("multiply_two start");				\
		foreach(LIST, two, opf(iGetNumber(n), "times", iGetNumber(two), "makes", iGetNumber(n) * iGetNumber(two))); \
		Memsay("multiply_two end");				\
	}


#define multiply_one()					\
	{						\
		Memsay("multiply_one start");		\
		foreach(LIST, one, multiply_two(one));	\
		Memsay("multiply_one end");		\
	}


#define handle_touch()							\
	{								\
		LIST = [];						\
		int n = iMany;						\
		LoopDown(n, Enlist(LIST, (int)llFrand(2000.0)));	\
		multiply_one();						\
	}

#else

void multiply_two(const int n)
{
	Memsay("multiply_two start");
	foreach(LIST, two, opf(iGetNumber(n), "times", iGetNumber(two), "makes", iGetNumber(n) * iGetNumber(two)));
	Memsay("multiply_two end");
}


void multiply_one()
{
	Memsay("multiply_one start");
	foreach(LIST, one, multiply_two(one));
	Memsay("multiply_one end");
}


void handle_touch()
{
	LIST = [];
	int n = iMany;
	LoopDown(n, Enlist(LIST, (int)llFrand(2000.0)));
	multiply_one();
}

#endif


default
{
	event touch_start(const int t)
	{
		Memsay("before multiplying");
		handle_touch();
		Memsay("after multiplying");
		LIST = [];
		Memsay("list emptied");
	}

	event state_entry()
	{
		Memsay("state entry");
	}
}


When you make this with the functions not inlined, the output is like:


[07:39:09]  Object: ( 50 kB ) ~> 55472 bytes free state entry
[07:39:15]  Object: ( 50 kB ) ~> 55472 bytes free before multiplying
[07:39:15]  Object: ( 49 kB ) ~> 55156 bytes free multiply_one start
[07:39:16]  Object: ( 49 kB ) ~> 54976 bytes free multiply_two start
[...]
[07:39:20]  Object: ( 49 kB ) ~> 54802 bytes free multiply_two end
[07:39:20]  Object: ( 49 kB ) ~> 54802 bytes free after multiplying
[07:39:20]  Object: ( 49 kB ) ~> 54802 bytes free multiply_one end
[07:39:20]  Object: ( 50 kB ) ~> 54802 bytes free list emptied


With the functions inlined, the numbers look a bit different:


[07:39:41]  Object: ( 51 kB ) ~> 57008 bytes free state entry
[07:39:51]  Object: ( 51 kB ) ~> 57008 bytes free before multiplying
[07:39:51]  Object: ( 51 kB ) ~> 56696 bytes free multiply_one start
[07:39:51]  Object: ( 51 kB ) ~> 56696 bytes free multiply_two start
[...]
[07:39:56]  Object: ( 51 kB ) ~> 56510 bytes free multiply_two end
[07:39:56]  Object: ( 51 kB ) ~> 56510 bytes free multiply_one end
[07:39:56]  Object: ( 51 kB ) ~> 56510 bytes free after multiplying
[07:39:56]  Object: ( 51 kB ) ~> 56510 bytes free list emptied


This example has only three functions, and inlining them saves between 1536 (by state entry) and 1708 (after the script has run) bytes. I don´t understand how these numbers come together when I look at this page --- unless declaring a function does "automatically take up a block of memory (512 bytes)".

As said before: Test with your "real" sources.

When inlining functions, you need to make sure that the names of variables do not overlap. Otherwise, you may generate scripts in which your code unexpectedly works with different variables when it is suddenly inlined.

The LSL compiler will not give you error messages about already declared variables when you keep the enclosing brackets ("{}") from the function definition with the define: The opening bracket creates a new scope; the closing bracket closes the scope. (Creating a new scope doesn´t appear to use memory by itself. You could make use of that.)

This example keeps the brackets with the defines. This is to show that you can convert functions into macros with minimal effort and to keep the code identical for the test. The brackets from the functions are not needed with the defines. You should remove them from the defines when you inline functions. Removing them makes it less likely that overlapping variables go unnoticed.

You can find this example in the git repository that accompanies this article in "projects/inline".

Use Parentheses

When you look at this simple macro


#define times3(x)                  x * 3


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


#define times3(x)                  x * 3
[...]

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


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


#define times3(x)                  x * 3
[...]

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


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


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


You need to use parentheses:


#define times3(x)                  (x) * 3


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


You need even more parentheses, because:


#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));


(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:


#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));


((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:


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


is ambiguous. What I meant is:


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


That is not ambiguous. Since "&&" usually takes precedence over "||", it´s different from the version without parentheses. It probably doesn´t matter with LSL for the bug mentioned above, but I´m not even trying to figure it out. It does matter in C and perl at least 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 and use parentheses. 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


cpp your_script.lsl


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


cpp -P your_script.lsl > your_script.i


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 seperate directory, perhaps with other headers you frequently use. You don´t want to specify in which directory you keep your "lslstddef.h" or other headers in all your sources: The directory might change.

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

The difference between '#include "lslstddef.h"' and "#include <lslstddef.h>" is that '#include "lslstddef.h"' will make cpp look for "lslstddef.h" in the current directory (where your source is) while "#include <lslstddef.h>" makes it look in default directories. Such directories can be specified with '-I'.

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:


.
|-- bin
|-- clean-lsl.sed -> make/clean-lsl.sed
|-- components
|-- example-1
|-- include
|-- lib
|-- make
|-- memtest
|-- rfedip-reference-implementation
|-- template-directories
`-- your-script


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 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:


[~/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/dep’ -> ‘your-script/dep’
‘template-directories/Makefile’ -> ‘your-script/Makefile’
[~/src/lsl-repo/projects]


Make sure that all files and directories from the "template-directories" are copied. When something is missing, the build system may not work.

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


#include <lslstddef.h>


default
{
	event touch_start(int t)
	{
		afootell("hello cpp");
	}
}


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


[~/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]


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


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


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:


[~/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]


So much effort to finally create such a little LSL script? No --- the effort here lies in giving you a reference which is supposed to help you to better understand the build system.

The effort to create an LSL script is no more than copying the "template-directories", saving your source in the "src" directory of the copy and running 'make'. You got a build system that does everything else for you when you run '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."

Make uses 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.

Suppose you have written a source "example.lsl". It includes "lslstddef.h" and uses a function defined in "getlinknumbers.lsl". Without Make, you´d have to run cpp to preprocess your source and use sed to clean it up. Then you´d have to make an entry in the look-up table so you can have the script replaced automatically. If you want to see the debugging version of the script, you´d use astyle to format it nicely. Since you want to use the compressed version of the script, you´d use i2o.pl to create the compressed version. Finally, you modify the source to fix a bug you found when testing the script. Except for making an entry in the look-up table, you´d have to do all the steps again.

Suppose you modify "lslstddef.h" or "getlinknumbers.lsl". You´d have to remember that you did this and that you need to process your source again. You might have several sources because the object you´re working on has several scripts. Some of them do not include "getlinknumbers.lsl". Which ones do you need to process again because they include a file you have modified, and which ones not?

Make figures out from the Makefile how to process your sources. It also figures out which source files do need to be processed again and which ones don´t when files the sources include have been modified. Instead of running a bunch of commands, you simply run 'make'.

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. The section "Repository" has some details.

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-script.lsl"

Assume you want to try out the "your-script.lsl" source from above: Enter the directory for the project ("/projects/your-script/") and run 'make' --- or load the source into emacs and run make from within emacs. I´ll do it with emacs:


C-x-f ~/src/lsl-repo/projects/your-script/src/your-script.lsl

M-x compile


Emacs now asks you for the command to use and suggests 'make -k'. This won´t work because Make needs to be run from within one directory above, i. e. "~/src/lsl-repo/projects/your-script/" rather than "~/src/lsl-repo/projects/your-script/src". So instead of 'make -k', you need to use 'cd ..; make -k'. Just edit what emacs suggests, then press Enter.

Emacs will check if it has any files that haven´t been saved yet because you might have forgotten to save your source. If there are files to save, it will ask you whether to save them or not. Then emacs will run make and open a new buffer to show you the output of 'make':


-*- mode: compilation; default-directory: "~/src/lsl-repo/projects/your-script/" -*-
Compilation started at Sat Jan 11 15:21:51

cd ..; make -k 
generating dependencies: dep/your-script.di
generating dependencies: dep/your-script.d
preprocessing src/your-script.lsl
  8 153 dbg/your-script.i.clean

Compilation finished at Sat Jan 11 15:21:52


When you need to process your script again, you can also do this within emacs: M-x recompile. I have bound the function to C-f1, so I´d just press Ctrl+f1. For an example of how to do such a keybinding, please see "emacs/emacs" in the git repository that accompanies this article.

To try out the script you just created in sl, you can switch to your sl client and make a box. Make a new script in the box and open the new script. In the built-in script editor, type into the first line:


// =your-script.o


Now click on the "Edit" button of the built-in script editor. Provided that you successfully prepared the automatic replacement of your scripts as described above, the compressed version of your script appears in the built-in script editor. It will be compiled by the sl server and run --- or you will see an error message in the built-in script editor.

astyle

Astyle is a nice tool to format your code so that it´s easier to read. Since you write your sources so that they are easy to read anyway, you may not want to use it to reformat your sources --- unless some lines are so long that they don´t fit very well on wiki pages maybe.

However, the output of cpp isn´t too readable. Therefore, the build system uses astyle to reformat it when it generates debugging versions of the scripts (in dbg/*.i). This is entirely optional.

sed

Sed is used by the build system for a number of things. One of these things is what you might call postprocessing: Preprocessing your sources can leave artifacts in the output of cpp due to the way macros (see "lslstddef.h") are used.

Sed is used to remove these artifacts, i. e. lines like "};" and ";". A closing bracket doesn´t need to be followed by a semicolon, and lines empty except for a semicolon are obsolete. Postprocessing with sed replaces "};" which "}" and removes empty lines.

The rules used by sed for this are in a so-called script which you can find as "projects/make/clean-lsl.sed" in the git repository that accompanies this article.

i2o.pl

The perl script i2o.pl comes from the source of the Open Stargate Network stargates.

It "compresses" your scripts by removing whitespace and by shortening the names of variables. Since variables are renamed, the compression cannot be undone entirely (like with astyle). Perhaps "shrinking" is a better word for this than "compressing".

The point of shrinking your scripts is saving memory. You cannot save (i. e. upload) a script (or contents of a notecard) that takes more than 64kB: The script (or the notecard) gets truncated, probably by the sl server. Cpp already removes comments and blank lines from the source, and i2o.pl shrinks the script further.

Side effects of shrinking are that sometimes a script appears to need less script memory to run, and shrunken scripts appear to run a little faster. I haven´t really tested either, and both doesn´t seem to make sense because the scripts are compiled to byte-code anyway. Test it if you like; it would be interesting to see the results. I always use the shrunken version unless I need to look at the debugging version, and it works great.

git

Git isn´t really involved here, other than that the build system and examples described and used in this article --- and the article itself --- is available as a git repository for your and my convenience. It´s convenient for you because you can easily download it.

It´s convenient for me because git makes it easy to keep the repository up to date. If several people would contribute to what is in the repository, it would be convenient for them as well because git makes it easy to put together the work done by many people on the same thing.

It can be convenient --- or useful --- for you because it makes it easy to keep track of changes. When you create a git repository from or for the sources of a project you are working on, you can not only apply (commit) every change to the repository accompanied by an entry in a log which helps you to figure out what you have done.

When you make a commit, it is a good idea to make a commit for one particular thing or objective, like "fixed the bug with too many numbers". It is irrelevant how many changes you made or how many files you changed to achieve the objective. Git is not about what happened to individual files but about what happened to the project. Still you can track every change to every single file and let git show you exactly what has changed.

You can also undo changes by going back to previous commits and experiment with variations (by creating so-called branches). Git works for this much better than making backups or copies of your sources. With backups or copies, it doesn´t take long before I can´t remember what a backup or copy was about and what has changed and whether the copy has become obsolete or not.

These features can make it worthwile to use git even when you don´t share your sources or don´t have several people working on the same project. There is lots of documentation about git, and one place you may want to check out is the documentation.

Emacs supports git out of the box, see here and there.

Using CVSs like git used to be awful even when you merely wanted to get a copy: There were many different ones that used slightly different commands to download a copy of the repository --- and yet different commands when you wanted to update a copy, perhaps a year later, you already had. You could either spend half an hour or so to figure out how to update your existing copy or download everything again, which might also take half an hour or so.

Git works much better. It seems that a lot of repositories have been migrated to git. Don´t let your perhaps frustrating experiences with CVSs keep you from giving git a try.

References

Ratany Resident 10:51, 13 January 2014 (PST)