Object Oriented LSL

From Second Life Wiki
Revision as of 08:53, 19 April 2007 by Xoren Raymaker (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

This article is a feature request

This should be straight forward: Classes in LSL.

Because it might not be clear why this is needed or how this would fit into currently existing LSL, I will try to make this clear through an example.

To keep it simple, this object has two prims, one base and one prim simulating the laser light. The laser light prim contains two scripts and the base prim contains one script, which uses code, written in the two other scripts.

Class Declaration

This is the first script, containing the interface of a script library, containing the interface of a class.

!type libraryinterface;

!exports class Laser;

class Laser: inherits llcConePrim
{
  protected
    function SetPosition(vector pos); override;
  public
    constructor Create(); override;
    destructor Destroy(); override;

    function SwitchOn();
    function SwitchOff();

    function SetColor(colorandalpha rgba); override;
    function GetColor(): colorandalpha; override;

    property Color: colorandalpha; read GetColor write SetColor;
}

This script introduces a few things yet unknown in LSL, none of which are (at the time of writing) documented feature requests. First line in this script specifies that this script is of a different type than a normal script. It is in fact the interface of a script library. It knows a counterpart called a libraryimplementation, which we will find in the second script. This is done because someone who wants to use the script library, needs to know it's interface, so these interface scripts need to be viewable by other users, but you might want to protect the implementation part of the library, so hence two scripts are needed, so that one can be viewable and the other not viewable. The next line we find, contains an !exports keyword, which says "this script exports something that should be able to be used by other scripts". In this case, it exports the class "Laser", which is defined after this. Note: The keywords !type and !exports are both prefixed by an exclamation mark (!) because they tell something about the script itself. They could be seen as extra (optional) properties to a script. Note2: I will not explain in depth here what everything means, i will assume therefor that the general concept and terminology of classes is known.

The Keyword Class

Now the actual subject of this feature request. The next line after the !exports keyword begins the definition of a class called "Laser". Straight into it, we also specify that it inherits from llcConePrim, which would be the base class representing all cone shaped prims. The llcConePrim itself then inherits from the basic llcPrim which would be the base class for all prims.

The Keyword Protected

Inside the class declaration, we find the keyword protected. This specifies that things defined after this keyword not available for use by all other code. Only members of the class itself, classes which inherit from this Laser class and classes which are "friends" of this Laser class, can "see" (and therefor use) the protected members declared.

The Member Function

A class would be worth almost nothing if it didn't have member functions. Not much is to be said about these. The keyword "override" that follows it's declaration means "this functions overrides the functionality of a memberfunction with the same name in the base class". Of cause this overriding can only be done if the function with this same name was defined as virtual in any of the ancestors. Besides override, there are many keywords to be invented here for use, like "reintroduce", "abstract", "overload", etc.. I think that the idea of "abstract" (pure virtual) and "overload" is clear to most people. The keyword "reintroduce" hides any functions of the same name from the inherited classes. Lets say a class A contains a virtual function "Test" with a parameter name as a string and a class B inherits from A and also implements the function Test, but does not have any parameters. You would then use the keyword reintroduce on the function B.Test, because this one reintroduces this functionality and you would not want a user of the class to use A.Test(...)

The Keyword Public

After the protected SetPosition member function we find the keyword public. This specifies that functions and such specified after this keyword are publicly available. In other words: every bit of code can "see" the things declared after the keyword public.

Constructor and Destructor

The constructor and destructor are special memberfunctions. Every class has a constructor and destructor, but not every class needs to do anything special inside the constructor or destructor. The constructor and destructor are guaranteed to be called when an object of this class is created or destroyed. This way the object can do some initialization and finalization.

Class Properties

Near to the end, we find a line defining a so called property with the name "Color". This works almost like a variable and we will demonstrate this later. When we set the value of this property, the function SetColor will be used and when we will read out the value of the property, the function GetColor will be used to return the value.


Class implementation

Here is the second script, which contains the implementation of the class defined in the previous section.

!type libraryimplementation;

!implements class Laser;

class Laser: inherits llcCubePrim
{
  private
    colorandalpha _Color;
    bool _State;
    key _id;
  protected
    function SetPosition(vector pos)
    {
      llSetPos(pos);
    }
  public
    constructor Create()
    {
      _id = llGetKey();
      _State = false;
      _Color = <0,0,0,0>;
      llPrimHide();
      llTargetOmega(<0,0,0>, 0, 0);
    }
    destructor Destroy()
    {
      // Nothing to do..
    }

    function SwitchOn()
    {
      _State = true;
      llPrimShow();
      llTargetOmega(<1,0,0>, PI, 1);
    }
    function SwitchOff()
    {
      _State = false;
      llPrimHide();
      llTargetOmega(<0,0,0>, 0, 0);
    }

    function SetColor(colorandalpha rgba)
    {
      _Color = rgba;
    }

    function GetColor(): colorandalpha
    {
      return _Color;
    }
}

I've use a few functions here that might not be actually existing functions, but for the sake of the simplicity of the example.. I don't know all functions yet, so i couldn't find a function for hiding and showing the prim the script is part of. This shouls be pretty self explanatory, except at the beginning, we see the keyword !implements. This new keyword tells the scripting environment that this script implements the Laser class. Further down this script, some people might wonder why the property Color was not present here. This is because properties are interface only. Think about it: What it there to implement on a property? A property only specifies how to read and (optionally) write the value.

Usage

Now perhaps the most important part: How are we to use these classes? Well, remember the !exports keyword, which said that the class Laser was exported? We will now use that exported class in the base object.

!type normal; // not needed, but one may specify if one wants to waste time of specifying this that are already default.. ;)

!include LaserCone.LibraryInterfaceScript;
!include LaserCone.LibraryImplementationScript;

Laser cone1, cone2;

default
{
  state_entry()
  {
    cone1 = Laser.Create();
    cone1.Color = <0,1,0,1>;
    cone2 = Laser.Create();
    cone2.Color = <1,0,0,1>;

    state off;
  }
}

state off
{
  state_entry()
  {
    cone1.SwitchOff();
    cone2.SwitchOff();
  }
  touch_start(integer num)
  {
    state on;
  }
}

state on
{
  state_entry()
  {
    cone1.SwitchOn();
    cone2.SwitchOn();
    llSetTimerEvent(1);
  }
  touch_start(integer num)
  {
    llSetTimerEvent(0);
    state off;
  }
  timer()
  {
    float r = llFrand(1);
    float g = llFrand(1);
    float b = llFrand(1);
    cone1.Color = <r,g,b,1>;

    float r = llFrand(1);
    float g = llFrand(1);
    float b = llFrand(1);
    cone2.Color = <r,g,b,1>;
  }
}

Well, most of this should be a piece of cake, really, except, perhaps, the lines containing the !include keyword. This keyword specifies that this this script would like to use code in a different script. In this case, I immediately went a step further than the basics, including a script which is not part of the content of the same prim, but which is inside a different prim. The prim which contains the scripts in this example is called "LaserCone" and the two scripts are called LibraryInterfaceScript and LibraryImplementationScript. Any script from any prim in the set may be included, but only things that are actually exported can be used. In this case, the implementation script does not export anything. It only implements the interface of the Laser class. The interface script does export something: the Laser class, so the last script may use it.

In the state_entry() of the default state, we see two Laser objects are created, stored in the variables cone1 and cone2. Also their Color is set. Pure green for cone1 and pure red for cone2. The way the color is specified here is not currently possible in LSL, but i like to think about color as a 4D vector, including red, green, blue and alpha values, which makes it easier to specify color. Also, OpenGL accepts color this way through glColor4f(r, g, b, a), so it might be better, performance wise, to do it like this. Here, i mostly used it for the sake of the example. It also makes the example class a lot tighter than including a seperate alpha property and set and get methods. Note: From this script, it is impossible to call the SetPosition function, defined in the interface of the class. This is not possible, because this function is defined in a protected visibility and this code is not part of a decendantclass, friend class, or other such code that should be able to see protected functions of the Laser class.


Authors

Original author: Xoren Raymaker, April 18, 2007.
I realize this might need some tweaking, but i'm interested what your ideas and comments are about this idea of mine. It is a big thing to add to SL, but i think it could (in time) greatly enhance flexibility and features.


Related ideas

  1. Operator overloading