Shipbrook OpenSim

Adventures in an open-source virtual world

Experiences, tips and tricks derived from running the OpenSimulator virtual-world server software. I can be found inworld on the OSGrid as Warin Cascabel.

Wednesday, August 19, 2009

Quick Tip: Frosted Glass

For a nice "frosted glass" look, make your window solid black, with a transparency of 10 and a glow of 0.55.

Wednesday, August 12, 2009

Updating with git

Before the change from svn to git, I had a nice little system set up; each new release was downloaded into its own directory, so I could (a) build a new version while the simulators were still running the old version, and (b) keep a few older, known-good versions around in case I needed to do a quick revert due to bugs.

With the move to git, however, the version numbers went away, being replaced with hash values - long hexadecimal strings which were not in any kind of alphanumerical order. This, obviously, made the old paradigm a bit difficult.

So I threw together a bash script to give me the same basic functionality; after downloading and building the latest version of the master branch, it copies the bin tree into a new numbered subdirectory of my builds directory, incrementing the number each time to give me a local "version number".

#!/bin/bash
#
# OpenSim updater v1.1 by Warin Cascabel
#
# This script pulls down the latest master from git, and if there are any
# changes, builds it and copies the bin directory elsewhere (it keeps them
# in local "version" directories, auto-incrementing the version number
# each time). It also copies the ossearch binaries into the bin directory.
# 
#
#
oshome="/public/opensim"                 # location of opensim root
buildsdir="$oshome/builds"               # where all the built versions are stored
devdir="$oshome/gitdev/opensim"          # where the git files go
searchbinloc="$oshome/search/trunk/bin"  # where the compiled ossearch files are kept
nextrev=$((`ls -b $buildsdir | sort -rg | head -n 1` + 1)) # get next local rev number
revdir="$buildsdir/$nextrev"             # where to place this particular version

# fail() prints an error message and exits with a nonzero status.
#
fail ()
{
  echo "$1; exiting."
  exit 1
}

cd $devdir
git checkout master
currenthash=`cat .git/ORIG_HEAD` 
git pull || fail "Unable to pull updates"
newhash=`git log -n 1 --pretty="format:%H"`
if [ "$currenthash" == "$newhash" ]; then exit; fi

#
# Clean and build. I do nant twice because sometimes it mysteriously fails, but the
# second time through it works. I don't know why.
#
nant clean || fail "Unable to clean previous build"
./runprebuild.sh
nant || nant || fail "Unable to build"

#
# Copy the files to our local version directory.
#
echo Compile successful\; copying files to $revdir
mkdir $revdir || fail "Unable to create directory $revdir"
cp -R bin $revdir/ || fail "Unable to copy bin files"
git log -n 1 --pretty="format:%H %ci" > $revdir/bin/.version

#
# Copy the ossearch binaries into the bin directory. Comment this section out if you
# don't want to use ossearch.
#
echo Copying search binaries...
cp -R $searchbinloc/* $revdir/bin/ || fail "Unable to copy search files"

#
# If we got here, everything worked!
# 
echo Completed successfully\!
exit 0

Sunday, July 12, 2009

Secret Dynamic-Texture Functions

One of the neat advantages OpenSim has over Second Life is "dynamic textures", or the ability to programmatically generate textures via script commands: draw lines, simple shapes, and text in a variety of colors.

These are done with a set of functions, such as osDrawLine(), osDrawEllipse(), osDrawText(), and so forth. There's an osSetFontSize(), which allows you to vary the size of the text, but you're stuck using the fairly boring default sans-serif font.

Or are you?

Above is a "guest book" version of my visitor logger, which uses a font named "Campaign" to display the last twenty-six visitors in its master list, but using a highly embellished calligraphic font. Here's how it does that:

The dynamic texture drawing functions use a string containing a command list for the vector drawer. Most of the functions, such as osDrawLine(), are simply wrappers which modify this string. For example, this:

  DrawList = osDrawLine( DrawList, 100, 100 );

really does nothing more than:

  DrawList += "LineTo 100,100; ";

Or, more accurately, the C# equivalent of the LSL code:

  DrawList += "LineTo " + (string) 100 + "," + (string) 100 + "; ";

For some reason, whether by accident or by design, wrappers for two of the vector render module's capabilities were never written as OSSL functions.

FontName allows you to set the font to anything that's installed on the system running OpenSim. This is the "display name" of the font (e.g. Palatino Linotype), rather than the filename (e.g. pala.ttf):

    DrawList += "FontName Palatino Linotype; ";
FontProp allows you to set various font properties (Regular, Bold, Italic, Underline and/or Strikeout), using the first letter of the desired property or properties, with multiple properties separated by commas:

    DrawList += "FontProp B,I,U; ";

A brief note about syntax: each texture-drawing command ends with a semicolon, to separate it from the commands which follow. The wrapper functions follow the semicolon with a space, though in practice this is not an absolute requirement. Do not put a space after the comma which separates parameter parts (such as "B,I,U" in the example above).

Once your command string is complete, you'll actually generate the dynamic texture with a function such as osSetDynamicTextureData() or osSetDynamicTextureDataBlendFace(). All of these functions have a parameter named "extraParams", which allow you to exert a little more control over the generated texture.

These extra parameters are name:value pairs, separated by commas. For instance:

"width:1024,height:512,bgcolour:blue"

Each possible parameter is shown below:

width: the width of the image in pixels, from 1 to 1024. Keep this value to powers of two (1, 2, 4, 8, 16, 32, 64, 128, 256, 512 or 1024), or you won't get what you were expecting - when I experimented with some other values, I received an ugly smeared blur which never resolved to a crisp, clean texture.

height: the height of the image in pixels, from 1 to 1024. Again, keep this to a power of two.

bgcolour: sets the background color of the image. This can be either one of the .NET named colors (e.g. Red, Blue, SpringGreen, etc.) or an ARGB (alpha/red/green/blue) 32-bit hexadecimal number (e.g. ffff0000 for red, ff00ff7f for Spring Green, etc.). ALWAYS set the alpha to FF (255) for this value, otherwise you'll just get black.

alpha: alpha channel value of the background, from 0 (fully transparent) to 255 (fully opaque). Setting an alpha channel seems to force the background of the image to black, rather than the default white, giving you a grey or nearly-black background for any value from 1 to 254. If you use the bgcolour parameter, alpha will override it.

altdatadelim: Change the separator for the command list from semicolon to any other value. This is essential if you want to display text containing a semicolon; simply change it to some other character that you know will not be in your text string. However, if you use this parameter, you'll have to build your command strings by hand rather than using osMoveTo(), osDrawText(), etc., since they are hardcoded to use the semicolon.

As an aside, you can use the ARGB hexadecimal color specifiers in the osSetPenColour() function (or the PenColour drawing command). In that context, the alpha channel does work, so if you want to draw in a half-transparent red color, you can use:

    DrawList = osSetPenColour( DrawList, "80ff0000" );

or

    DrawList += "PenColour 80ff0000; ";

Sunday, July 05, 2009

Multi-simulator setup

My OpenSim setup is a bit of a beast. I've currently got nine regions running, spread out amongst six machines. One machine has two instances running (one region per instance), and another has three regions running in the same instance; the rest are each running one region each.

To make life simpler for me, all of them are running from a public directory on my file server (which also hosts their MySQL databases), which every machine has mapped to /public. (That's the root of the public directory; the OpenSim installation is /public/opensim.)

Every time I upgrade OpenSim, I do a fresh checkout into /public/opensim/builds/####, where #### is the revision number. I tend to keep twenty to thirty revisions around at any one time; if there's a major issue with the newest revision, I can immediately revert to a previous one which is known to be good. (As long as there hasn't been a major database schema change, of course.) Keeping a few versions around also helps to narrow down when a particular bug was introduced.

I start OpenSim with a script (about which I'll go into more detail later) which ordinarily looks for the highest-numbered subdirectory of builds, and then runs from that directory. Should the region crash, or if I type "quit" at the console, it checks again in case I've built a newer version, and starts the region again.

To avoid having to copy all of the configuration files into each revision's bin tree every time I upgrade, I've also got a /public/opensim/configs directory. In here is a master copy of the OpenSim.ini file, actually named master.ini, which contains all the common settings which are shared by each of my regions. There's also a number of subdirectories which contain the data for each region: a small OpenSim.ini containing just the values specific to each particular instance, and a Regions/ subdirectory containing the region descriptor .ini file (formerly an .xml file).

The directory tree is set up like this:

/public/opensim/
   stayup
   builds/
       9952/
       9959/
       9962/
       9964/
       9969/
       9970/
       9972/
       9974/
   configs/
       master.ini
       athena/
           OpenSim.ini
           Regions/
               Quirm.ini
       boomer/
           OpenSim.ini
           Regions/
               Pseudopolis.ini
               StoLat.ini
               Uberwald.ini
       config-include/
           CenomeCache.ini
           FlotsamCache.ini
           Grid.ini
           GridCommon.ini
           GridHypergrid.ini
           Standalone.ini
           StandaloneHypergrid.ini
       ecalpon/
           OpenSim.ini
           Regions/
               Ecalpon.ini
       erewhon
           OpenSim.ini
           Regions/
               Erewhon.ini
       [...]
    oars/
        20081109/
            Erewhon.oar
            Ecalpon.oar
            Theseus.oar
            Quirm.oar
        20091114/
            [...]

The subdirectories under the configs directory are named either for the machine hosting the instance, or (on the machine hosting two instances) named for the region hosted by the instance. There's also the config-include subdirectory, which contains the newer subordinate .ini files that were recently added (modified to use absolute paths rather than the paths relative to the bin directory).

The smaller OpenSim.ini files look like this:

[Startup]
regionload_regionsdir="/public/opensim/configs/athena/Regions/"
storage_connection_string="Data Source=fileserver;Database=quirm;User ID=opensim;Password=********;";

[Network]
http_listener_port = 9506
remoting_listener_port = 8894

The regionload_regionsdir keyword tells it where to find the region descriptor files used by the instance; storage_connection_string points to the machine hosting the database ("fileserver") and identifies the database, MySQL username and password.

The master and smaller .ini files are specified on the OpenSim command line with the -inimaster and -inifile switches, respectively

Now, to get things running, I ssh in to each machine, start a screen session (so the simulators will keep running if my connection disconnects), change to the /public/opensim directory, then run the stayup script.

Most of the time, I just run it like:

./stayup

Without any parameters, it runs the highest version it can find in the builds subdirectory, using the master.ini file and the small OpenSim.ini file found in /public/opensim/configs/machine-name subdirectory.

For the machine running two instances, though, I can override the location of the smaller OpenSim.ini by specifying the name of the configs subdirectory:

./stayup ecalpon
If I want to peg the region to running one specific revision, rather than checking for the highest revision number each time it restarts, I can specify the revision with -r####:

./stayup -r9972

And, of course, the parameters can be combined:

./stayup ecalpon -r9972

This keeps things running fairly well. Ordinarily, if I'm not pegging the simulators to a specific version, I can check out and build a new revision while all the regions are still running, then type "quit" at one of the consoles so it loads up the newly built revision. If nothing obviously bad occurs during the startup, I can then tell each of the other simulators to quit, so they restart with the new revision.

#!/bin/bash
#
# stayup script - runs a region server of the most current OpenSim build (or a specific
#                 version, if specified as a parameter) forever. If you shut down the
#                 region server, and have not explicitly specified a version to run, it
#                 will check for the most recent version again. Uses a separate directory
#                 containing the OpenSim.ini and region xml files, keyed off the hostname
#                 of the machine running the script (again, unless otherwise specified)
#
# Jeff Lee (Godfrey in IRC / Warin Cascabel inworld), 8 September 2008
#
# Usage: stayup [-r9999] [config-file-location]
#
# NOTE: This script requires particular directory structures in order to function.
#
# The buildsdir variable contains the path to a directory which contains nothing
# but OpenSim builds, named exactly as the revision number (e.g. 6151, 6152, etc.)
# This can be changed below, but the directory still must contain only subdirectories
# consisting of the revision number.
#
# The configsdir variable contains the path to a directory which contains subdirectories
# named after the hostnames of the machines running OpenSim.  Each of these subdirectories
# should contain the OpenSim.ini and the region xml files for the server. Furthermore,
# the regionload_regionsdir directive in OpenSim.ini should point to the directory
# in which OpenSim.ini and the region files are located.
#
# You can specify a different revision to run with -r9999, where 9999 is the revision
# number to run.
#
# And you can specify that a server run a different region's files by specifying the
# hostname which normally runs them.  The order of these two parameters does not matter.
#
# Also, please note that if ls -b $buildsdir returns anything other than the bare
# subdirectory name (e.g. 6151, 6152, etc.), this script will fail.

parseparam ()
{
 if [ ${1:0:2} == "-r" ]
 then
     revision=${1:2:10}
 else
     config=$1
 fi
}

while [ 1 ]
do
 buildsdir="/public/opensim/builds"
 configsdir="/public/opensim/configs"

 revision=`ls -b $buildsdir | sort -rg | head -n 1`
 config=`hostname`
 if [ -n "$1" ]; then parseparam "$1"; fi
 if [ -n "$2" ]; then parseparam "$2"; fi
 if [ -n "$3" ]; then parseparam "$3"; fi
 if [ -n "$4" ]; then parseparam "$4"; fi
 echo Executing revision $revision with config files for $config
 if [ ! -e "$buildsdir/$revision" ]
 then
     echo Error: revision $revision does not exist\; terminating.
     exit
 fi
 if [ ! -e "$configsdir/$config" ]
 then
     echo Error: Configuration directory $configsdir/$config does not exist\; terminating
     exit
 fi

 cd $buildsdir/$revision/bin
 mono OpenSim.exe -inimaster=$configsdir/master.ini -inifile=$configsdir/$config/OpenSim.ini
 echo Sleeping for three seconds before restarting...
 sleep 3
done

I keep a Konsole window open on my desktop, with ssh connections to each simulator instance's screen session in its own tab (plus a tab sshed in to the file server for building new revisions, and a general-purpose bash tab on the local machine). This reduces overall screen clutter (only one window in the task bar), and lets me maintain constant connections to all the simulators. I can easily check the status of each simulator by hitting Shift-Right to cycle through each tab:

Friday, October 31, 2008

LSL Trick: low-impact Blinkenlights

When building a "freebie store" on the OSGrid (given that there's no economy on the OSGrid, there's really no other kind of store at the moment), I built "vendors" for my various items which keep track of how many they dispense. (I don't know why it matters, but I obsess over data collection.) Given that my scripts lose their states every time I upgrade, they store their information in a MySQL database on one of my servers. Of course, I also had to build a front-end to tell me, for example, how many toilets I've given away. And I made the inworld presence for this object resemble a server rack.

But of course I couldn't just slap a photo of my rackmount servers onto the front of a prim and call it a day; I had to get clever and make the hard drive lights blink randomly. So I wrote a simple script to make tiny spheres switch between glowing orange and non-glowing transparent, using llSetPrimitiveParams().

The problem, as I discovered this morning, was that the server console was filled up with useless messages, informing me every few seconds that it had saved the server rack object. This clearly wouldn't do. I needed a way to make them blink randomly, but without actually making changes to the object which would cause the object to be continually re-stored in the database. Texture animation seemed the most likely method, since that's a client-side effect that gets stored with the prim.

So I created an image consisting of white dots on a transparent background.* I then applied that texture to the "blinkenlight" prims (which I set to an orange color, full bright and glowing), and added the following minimal script:

//XEngine:lsl
default
{
    state_entry()
    {
        llSetTextureAnim( ANIM_ON | LOOP, ALL_SIDES,
                          64, 64, (integer) llFrand( 4096.0 ),
                          (integer) llFrand( 2048.0 ) + 2048, 10.0 );
    }
}

Much better solution - not only does it avoid console clutter, it imposes much less script lag on the region. When applied to multiple prims, they each blink on a pseudo-random pattern of random length, and starting at a different position in the blink sequence.

The only drawback is that texture animation is suppressed if the animated area is smaller than a certain area on the screen, so you only see the blinking if your camera moves close enough to them. Which I think is an acceptable trade-off, if imperfect.


* The steps I used in creating the image (in The GIMP) were as follows:

  1. Create a 64x64 image with a white blackground
  2. Change the image mode to indexed, 2-bit (black and white)
  3. Apply the Hurl noise filter with a randomization of 10% and a repeat of 1
  4. Invert the colors (producing white dots on a black background
  5. Change the image mode back to RGB
  6. Decompose the image into RGB channels
  7. Recompose the new image as RGBA, using any of the other three channels for alpha
  8. Resize the recomposed image to 256x256, with no interpolation.