Second Life: A Programmer's Perspective

In Second Life, you can create objects that have credible dynamism, even compared to AJAX and browser-based applications.


April 05, 2007
URL:http://www.drdobbs.com/open-source/second-life-a-programmers-perspective/open-source/second-life-a-programmers-perspective/198800545

Dana is a division scientist with BBN Technologies and is an expert in the fields of peer-to-peer and collaborative computing, software agent frameworks, and assistive environments. Dana's identity in Second Life is ElectricSheep Expedition. Raymond is a software engineer with BBN Technologies where he has designed and developed a variety of web applications and other distributed systems. He can be contacted at [email protected]. Dana and Raymond's most recent book is Professional Rich Internet Applications: AJAX and Beyond (Wrox, 2007).


Having come up to speed in Web 2.0 concepts such as AJAX, Ruby on Rails, and TurboGears, many developers have begun considering the world beyond Web 2.0, asking: "If there's a 'Web 3.0', what is it, and what does it look and feel like?" One possibility is a next-generation Web embodied as a Rich Immersive Environment (RIE) which, instead of appearing to users as a two-dimensional form in a browser, steeps them in a three-dimensional world filled with places to see, avenues to walk, people to interact with, objects and environments to play with, things to buy, and services to access. Applications grounded in such environments require a completely different style of manipulation by—and interaction with—users. In short, you need to reconsider what the concept of "interface" means, and how to engage users with your work.

In this article, we examine what's involved in developing for Second Life (www.secondlife.com), an emerging RIE developed by Linden Lab (www.lindenlab.com). Second Life has captured both mindshare and a considerable amount of actual commerce revenue for the participants and creators of virtual value.

What is so compelling about Second Life and other emergent virtual (nongame) worlds? In a 2006 interview, Linden Lab CEO Philip Rosedale explains that when entering Second Life, people's digital alter-egos (known as "avatars") can move around and do everything they do in the physical world, but without such bothers as the laws of physics. "When you are at Amazon.com [using current web technology], you are actually there with 10,000 concurrent other people, but you cannot see them or talk to them," Rosedale said. "At Second Life, everything you experience is inherently experienced with others."

Think of what this would mean to a social site. Instead of posting entries and responses on slashdot.org or digg.com and then reading them from a web page or an RSS feed, imagine conversing in real time with actual peers on emerging stories from real Reuters or CNet news feeds. Imagine opening a storefront site for your next brilliant idea and having it literally be a storefront, where you could, in real time, interact with your user base and potential customers. Imagine a world in which you could multitask by first tending to personal activities from "home," turn attention to "work," then break for "lunch" in an RPG adventure or go to a virtual beach in the middle of the day.

That's the promise that Second Life suggests. But there are several other reasons why it should be on your developer radar:

Second Life: The Programmer's View

The primary attraction for software developers in using a platform like Second Life is the ability to create objects that have credible dynamism, even compared to AJAX and browser-based applications. Virtually every one of the objects you encounter in SL, from beach balls to shopping malls, has been created by a developer or team. A short (and incomplete) description of SL is that it is a huge simulator running a potentially enormous number of finite state machines (FSMs). The scripting environment controlling the execution of every one of the FSMs—yours and others—is a C-like language called "Linden Scripting Language" (LSL).

Essential Second Life Elements

Becoming an SL resident represented by your own avatar and spending time in the SL world is mandatory for anyone seeking to do serious SL development. In a real sense, you can think of time spent in SL as participating in a living, evolving operating environment for which you are creating artifacts often backed by your code. The more time you spend participating in SL, the better you understand the capabilities and limitations of SL's simulation and rendering engines. An interesting concept for developers is that you're living both inside an application and an IDE. Whenever you are in a coding mood, you press the Build button at the bottom of your screen and invoke the IDE.

You first need to download the SL client for your platform. There are clients for Windows, Mac OS X, and a Linux Beta. After installing, you create an identity (an avatar) for yourself. Every new avatar inherits a collection of objects, given to you by Linden Lab. Everything else you own has to be purchased or created by you or another resident.

When you create objects, you can hold them in your inventory, which is infinitely expandable and can stretch to hold your entire virtual clothing collection (plus an antique oak armoire to store it in). Of course, as your virtual storehouse gets bigger, the database fetch needed to populate it takes longer each time you connect with SL. There are techniques for compressing your holdings, but the best thing for you to do is to buy virtual land in-world.

If you buy and hold virtual land in SL, you can place whatever you want there and it remains. This rule applies to items you purchase or create. Thus, for example, even though your library of objects may contain interesting items and although you may create artifacts, you need to acquire land to give them permanent presence. This is how thousands of residents have created commercial revenue-producing sites that vend fashions, automobiles, and other virtual artifacts.

After going in-world, it is recommended that you teleport to one of the sandbox areas to build and test objects of your own creation in a sandbox. Then copy them into your inventory.

Because all sandboxes are wiped twice a day, make sure to copy your creations back into your inventory periodically ("save early, save often").

You can only create objects using in-world building tools (there are persistent rumors that eventually you'll be able to import external models), but you should create animations externally using Avimator (avimator.com), then import them in-world. You can use the syntax coloring editor in the object builder IDE, or use an external text editor (plug-ins exist for several popular ones).

The SL Object Model

The virtual world that is Second Life consists of a bank of servers, each of which is responsible for managing objects, terrain, and avatars, and for ensuring that clients connected to the server are updated in a timely manner. Each server coordinates the interaction between avatars and in-world objects. Objects do not have an ability to react to inputs from avatars or other objects; they have to be scripted to come to life. Ordinarily, your responsibility as a developer ends with writing the logic for an object, but in SL, creating a lively and responsive world combines two development disciplines—object creation and object scripting.

SL hosts a combination tool (in effect, an IDE), both for creating tangible objects and giving them the ability to react to their environment. There are no restrictions on the sorts of objects you might dream up. Every visual element from shape to material to texture is possible to specify and tweak. Everything you create is a series of simple or linked polygons. In fact, your avatar itself and everything it interacts with is either a single 3D polygon (a "prim" in SL speak), or a grouping of linked prims. Whenever you right-click on any prim, a circular menu is visible, revealing both the limited built-in behaviors and those that you have given it via scripting.

Figure 1 shows an avatar having right-clicked on a chair, and sets of choices appear as pie slices in a circular menu. The few choices available are a representative set of defaults. A logical choice in this case might be "Sit Here." More complex behaviors must be added by developers attaching responses to potential interactions. Scripting is done via the Linden Scripting Language.

[Click image to view at full size]

Figure 1: An avatar using the Second Life object menu.

LSL has evolved through a couple iterations, but currently seems to have a stable set of APIs in the SDK. Studying the API reference (lslwiki.com or mirrors such as http://rpgstats.com/wiki/index.php?title=Main_Page), you find a rich API for doing everything from imparting physical motion, collision detection, and movement, to connecting SL objects with the Internet.

Adding Behavior to Your Objects

Okay, now try scripting. Click the Build button and the IDE window is revealed. The cube shape and Create icon should be the defaults. Move the mouse cursor somewhere close to you and "rez" a cubic prim. While there are numerous shaping, positioning, and texturing features you can apply to a prim, focus on LSL scripting for now, beginning with an understanding of data types and moving to the structure of a script itself.

LSL Data Types

LSL supports the types in Table 1. Although some are obvious, others may surprise you. C programmers may be happy to see the explicit support for string operations, such as concatenate (via the "+" operator) and the usual operations supported for Java strings.

Data Type Usage
integer Whole number in the range -2,147,483,648 to 2,147,483,647.
float Decimal number in the range 1.175494351E-38 to 3.402823466E+38.
vector Three floats in the form < x , y , z >. Usually a position, color, or Euler rotation. An example declaration is: vector a = <1,2,3>;.
rotation Rotations consist of four floats, in the form <x,y,z,s>. The s term connotes an angular measurement.
key A UUID (specialized string) used to identify something in SL, notably an agent, object, sound, texture, other inventory item, or dataserver request.
string A sequence of characters, limited only by the amount of free memory available to the script.
list A heterogeneous collection of other data types, (excluding list itself).

Table 1: LSL data types.

In addition to C native types such as integer and float, LSL also explicitly supports the UTF-8 string type that behaves as you would expect (in Java, not C), and a key type for creating, manipulating, and representing globally unique identifiers (GUIDs). There are no user-defined constants, thus nothing like enum.

LSL is event-driven, features states, and supports 3D variable types (vector and quaternion). LSL has built-in functions for manipulating physics and social interaction among avatars. LSL adds some interesting keywords; for example, state, which is essentially a "go to" operator that forces a transition in a new state handler.

For representing collections, LSL supports only the list type. There are no arrays or dictionaries, and lists cannot contain other lists. Lists in LSL are sufficiently annoying that we provide an expanded description here.

The list type may contain heterogeneous elements comprised of lists of other primitive data types. Lists are created via comma-separated values (CSVs) of the other data types, enclosed by square brackets: "[" and "]." A typical list declaration would resemble:

// a list with a two integers, a 
// string, float, vector and rotation 
list l = [42,0, "electric sheep",
   3.1416,<1.0,2,0>,<0,0,0,1>]; 


Lists also store metadata about each list item, but also its type, accessed through list accessor primitives llGetListEntryType and llCSV2List (an annoyance with LSL is that every built-in function—and there are myriad—must begin with "ll," as if you somehow needed a reminder as to whose scripting environment you're using). Lists can only be read using accessor functions (rather than bracket notation). Thus, to retrieve the first element from the aforementioned declared list and cast it to a string, you might use:


string element; 
element = llList2String(l,0); 

Elements fetched from lists are accessed and cast to type in a single operation; llList2Float retrieves an element and casts it to float, llList2Integer casts the element to integer, and so on. One additional shortcoming: Because lists cannot contain other lists, multidimensional arrays are difficult to create and manage. One way to get around both the lack of arrays and the lack of an explicit object model is to use "list striding."

A "strided" list is simply a list with items grouped together in a fixed and repeating layout. If, for example, you needed in-memory containment to record visitors to your store, such a list might contain the visitor name, amount of Linden dollars spent, item purchased, and number of minutes spent on your premise. Although you would periodically persist this cache to a formal database, you might want a short-term cache. In this case, you might use a strided list:


integer STRIDE = 4; 
list patrons = ["Zeno Chambers",500, "Magic Broomstick","2006-10-31", 
  "Biggie Pike", 50, "Flagon of Pumpkin juice","2006-10-31",
  "Mae Moriarity",25,"Halloween Hangover Kelper","2006-11-01"]; 

Then you could use one of the strided list convenience functions to assist in managing the list. To sort (ascending) a strided list, you would use:


patrons = llListSort(patrons, STRIDE, TRUE); 

LSL Script Structure

If you select the Content tab, right-click and select New Script, you see a new script appear in the editor window. It should be identical to Listing One and your worldview should resemble Figure 2.

 1 default
 2 {
 3     state_entry()
 4     {
 5         llSay(0, "Hello, Avatar!");
 6     }
 7 
 8     touch_start(integer total_number)
 9     {
10         llSay(0, "Touched.");
11     }
12 }
Listing One

[Click image to view at full size]

Figure 2: Creating a new script.

Inspecting a default new script reveals a few things about LSL. First of all, LSL is essentially a C-like language, with roughly equivalent syntax and similar control flow. First, notice that the logic for the object is normally contained within a code block called "default." You might think of the default block as the "static void main" of LSL. The default module must always be present, and represents the event callbacks for the object in which it exists. Other states may be present either in this module or in others in the Contents folder, and it's possible to transition to some other state handler (and back) via the state keyword.

The default script has two handlers, state_entry and touch_start. Reviewing the comprehensive list of event handlers at the LSL wiki, you see that state_entry is invoked whenever the state is entered and on creation of the instance of the object. Touch_start is invoked whenever the object is touched (you'll need to save and compile the script and close the IDE to see the effects of the touch_start callback). In both handlers, the llSay function is called; llSay broadcasts its argument (a text string) to anyone within a 10-square-meters range. Now that you've seen the "hello world" script, it's time to try diving off the deep end. Delete the contents of the script for the object and replace it with the script in Listing Two.

 1 //This script forwards all surrounding chat to the via email
 2
 3
 4 string g_Mail_Addr = "somename@some_domain.com";
 5 integer g_email = FALSE;
 6 integer g_IM = FALSE;
 7 integer listen_channel = 0;
 8 integer command_channel = 2;
 9 float g_maxtimeout = 180.0;
10 string g_emailStateOn = "Recording has been enabled!";
11 string g_emailStateOff = "Recording has been disabled!";
12 string g_IMStateOn = "IM relay has been enabled!";
13 string g_IMStateOff = "IM relay has been disabled!";
14
15 default
16 {
17  on_rez(integer param) {
18      llResetScript();
19  }//on_rez
20
21  // toggle state during the touch handler
22  state_entry(){  // possible to listen on more than one channel?
23      llGiveInventory(llGetOwner(), 
           llGetInventoryName(INVENTORY_OBJECT, 0));
24      string owner = llGetOwner();
25      llListen( command_channel, "", owner, "" );
26      llListen( listen_channel, "", NULL_KEY, "" );
27      llInstantMessage(owner, g_emailStateOff+" Type: '/2 hear! 
          <email address>' to capture close proximity inputs.");
28      llInstantMessage(owner," Type: '/2 !hear' to disable capture.");
29      llInstantMessage(owner, " Type: '/2 IM!' to enable IM feedback.");
30      llSetTimerEvent(g_maxtimeout);
31
32  }//state entry
33
34  listen( integer channel, string name, key id, string message ){
35      string owner = llGetOwner();
36      if (channel == command_channel){
37          list messageContent = llParseString2List(message, [" "], []);
38          integer len = llGetListLength(messageContent);
39          message = llList2String(messageContent,0);
40          if(message == "!hear"){
41              g_email = FALSE;
42              llInstantMessage(owner, g_emailStateOff);
43          }//disable email  
44              if(message == "hear!"){
45               if (len < 2){
46               llInstantMessage(owner, "incomplete message: I need an 
                                          email address too");
47              } else {
48                  g_email = TRUE;
49                  g_Mail_Addr = llList2String(messageContent,1);
50                  llSetTimerEvent(g_maxtimeout);
51                  llInstantMessage(owner, g_emailStateOn+" sending to 
                                          "+g_Mail_Addr);
52              }    
53          }//enable email
54          if(message == "!IM"){  //default mode
55              g_IM = FALSE;
56              llInstantMessage(owner, g_IMStateOff);
57          }//disable IM
58          if(message == "IM!"){
59              g_IM = TRUE;
60              llInstantMessage(owner, g_IMStateOn);
61          }//enable IM
62      }
63      if(g_email){
64          if(g_IM){
65              //send IM to owner of chat channel relay if on
66              llInstantMessage(owner, message);
67          }
68          //send email to owner of chat channel relay
69          llEmail(g_Mail_Addr, "SL Listening", message);
70      }//end if(g_email)
71  }// end listen
72
73  timer(){ // on expire of timer, discontinue
74       g_email = FALSE;
75       g_IM = FALSE;
76  }
77
78 }//default
Listing Two

Connecting SL to Real Life

This script demonstrates both a typical finite state machine and some of Second Life's asynchronous communication capability. Second Life supports XML-RPC and HttpRequests. You can also write handler methods capable of sending/receiving e-mail message traffic. This exercise demonstrates the e-mail interface. You create an object (a "Chat Catcher") that exists in the virtual world, sitting apparently dormant, but reporting the ongoing chat to an e-mail address of your choice. To preclude the possibility of an e-mail flood, you set a short timer to shut off the object's listening capability.

The Chat Catcher script creates a listening channel (channel 2), and e-mails anything uttered to the e-mail address. Everything is contained within the default block (but again, it needn't be). The first dozen or so lines declare and instantiate useful global variables. The g_maxtimeout is set to three minutes, but you may want to experiment with this. The general chat channel (g_listen_channel) is 0. A private channel (g_command_channel) is set aside for the object to listen to commands from its owner.

On line 17, the script is reset using llResetScript. This is a good idea, especially if the script is attached to an object intended for sale or transfer. On transfer to a new owner, any listens set to the owner are reset, and all pending events are cleared when the object is rezzed by its new owner. After an llResetScript invocation, control is transferred to the default block, and its state_entry is triggered. When writing and debugging the script, you can achieve the same effect as specifying "reset" in the script editor.

Lines 22 through 31 are the initializers for the default script block. The script transfers ownership of the object to which the script is attached to the new owner. Next, lines 25-26, the object posts a listen on channels 0 and 2. Then, the object issues two private messages to the object owner, and sets the operational maximum time for the script to operate (lines 28-30). In the listen handler, whenever a message is received on channel 2, the object parses it (lines 35-52). The object is looking to receive one of the messages in Table 2 (typed into the chat window by the object's creator).

Command Description
/2 hear! emailname@email_address Forwards any conversation in the object's proximity to the e-mail address specified as the second argument:
/2 !hear Cancels any listens on the public channel (channel 0).
/2 IM! Private messages the general (channel 0) conversation to the objects owner, even if the owner is no longer in the proximity of the conversation (teleported to another place, for example).
/2 !IM Cancels the forwarding of public messages as private messages to the object owner.

Table 2: Chat Catcher commands.

On Line 36, the listener uses llParseString2List to break the input message into a list of strings. The command itself is the zero-th element of the list; this is pulled from the list on line 38. Note that the message channel designator (/2) is not a part of the message payload, and messages to the command channel must be prefaced with this command channel qualifier. Any messages, from the object owner or anyone else in proximity, are heard on channel 0, and no channel designator is needed for public chat messages.

If the "hear!" message is incomplete, this is checked in lines 44-46, and a private message is sent back to the object owner. If the message has an e-mail address, the e-mail address is teased from the message body (which by now has been parsed into a list). Line 38 splits the message body into a list with whitespace as the separator. This code doesn't check the e-mail address for valid syntax, but this could be easily done by turning the string into a list with the @ sign as the separator, and looking at the list length.

Lines 53-66 complete command channel processing to turn private messaging on/off. On line 68, an e-mail message is sent to the e-mail address provided.

Try this out on any object you create—say, a beach ball that encourages a crowd to pass it around, and you'll have one in your inventory courtesy of Linden Lab. Some privately owned land in Second Life might not permit you to instantiate the object.

Conclusion

Second Life is full of opportunities to create useful and beautiful artifacts with an unlimited range of behaviors. Try taking developer classes in-world, advertising to meet other developers, and joining one of the many developers' groups in-world. In no time, you'll find yourself amongst the creative elite of Second Life developers.

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.