JanetRossini.github.io

Lua, LSL, Blender, Python in Second Life


Project maintained by JanetRossini

Trains, Trams, and Roller Coasters

May 20, 2025 • [designluaobjectstesting]


With a language like SLua, we can do a much better job. To do that, we’ll need to think differently from how we think about LSL. Let’s begin to explore the differences.

We are just starting what will be a long-term project, converting our private and commercial trains, trams, and roller coasters—Oh my!—to SLua. We assume that SLua is still some months away, but we are active in working with the ALpha version that is now running on the, well, beta grid.

Second Life seems to refer to its Lua dialect as SLua. It is based on Luau, the scripting language of Roblox, which is itself based on Lua. In these writings, unless it’s necessary to distinguish these dialects, I’ll refer to them randomly as SLua, Lua or even occasionally Luau, just to keep us all on our toes.

Linden Scripting Language, LSL, really only has one data structure, the “list”. A list is a truly cumbersome thing that can contain primitive data elements, integers, floats, vectors, rotations. It cannot contain lists. To make up for its weakness, the list is very inconvenient to use. You cannot subscript into it like an array. You cannot index into it like a dictionary. And yet, despite those disadvantages horrors characteristics, people have been able to build marvelous things in Second Life, including the Valkyrie Transport trains, trams and roller coasters. (Oh my!)

With SLua we want to do better. In this article and surely others to follow, I’ll be writing about our work. I won’t be revealing all our secrets: my purpose here is to write about some of the design issues that a competent SLua developer might want to know about and use, and perhaps some of the detailed issues that aren’t particularly secret.

Like LSL, Lua has only one data structure. Unlike LSL, Lua’s is the table. A Lua table can behave like an array, indexed from 1 to wherever. A Lua table can behave like a dictionary, with keys and values, Here’s an example of that:

person = {
   name="Dave",
   age=34,
   eyes=-"hazel",
   hair="brown"
}

To print Dave’s age, we could do either of these things:

print(person.age)
print(person["age"])

These two forms mean exactly the same thing. person.age means person["age"], no more and no less.

Lua tables can contain any Lua data type: boolean, number, string, function, or table.

Other articles on this site may go into more detail about the Lua language, and there are courses, books, and web sites about it. Our purpose here is just to build up to this point:

Tables Can Implement Objects

Note
Second Life uses the word “object” to refer to the various cubes and spheres and meshes that make up the world. Here, we are referring to software objects, data structures in our scripts that hold related information and related functions. I’ll throw in the word “software” or “Lua” once in a while to help you remember.

Our purpose here is not to explain Lua objects to someone who doesn’t know them, though some examples here are pretty simple and should be helpful. Our purpose here is to describe why Lua objects are so useful to building our systems.

An object, oversimplifying a lot, is just a collection of variable values, and functions to manipulate those values and produce results. Since a Lua table can hold values and functions, and since they can have names like Dave’s age and hair color, a Lua table makes a perfectly good object. There are some clever internal mechanisms, such as the metatable, that allow all the objects of a given kind to share their functions and not their data. (We generally call an object’s kind “class”.)

We’ll see examples of this going on in the articles that are already here and in future ones. Our purpose here is to think about how to design with objects.

Designing with Lua Objects

When programming with Lua objects, our job is to come up Lua objects that make sense in terms of what we want to accomplish. In LSL, we are stuck with the numbers and strings and lists. In Lua, we can have any kind of object that we want. Picking useful ones is the trick.

Let’s take the example of a simple vehicle, a Bus. There is a Bus vehicle, an SL object that looks like a Bus. We want to lay out a Bus route on our land and have the Bus travel around that route, stopping at defined locations so that avatars can get on or get off if they want to. We’ll mostly limit our attention to the script or scripts that operate the Bus.

The Bus scripts will surely include the ability to move along a path. The Bus should be able to drive on any provided path, perhaps even paths provided in different forms. Certainly if we are going to support trains, trams, and roller coasters, the forms of path we might want will be quite varied. It would be ideal to have most of the script or scripts in the vehicle be independent of the form of the path. So we might come up with these two notions of object:

Bus

Path

In our present schemes, we represent the path in a few different ways. We define some paths using 3d curves in space. Others are defined by dropping “guides” at strategic points along the desired path.

In my current view, which is biased by what I know and don’t know, I would like for a vehicle to “think” in terms of its distance along a curved track with a known beginning and end. It would start at Distance=0 and given a speed of d meters per second, it would move to Distance+d every second. (Details of making this look smooth left out for now.)

This ideal vehicle would just increment Distance from zero to the maximum distance and then stop, or if the path was closed, set Distance back to zero and continue.

I’m just making this up but maybe the Bus would have code like this for moving.

-- sketch, not compiled or tested
Bus = class()
function Bus:init(path)
   self.path = path
   self.distance = 0
   self:move(0)
end

function Bus:move(step)
   self.distance += step
   local pos = self.path:position_at_distance(self.distance)
   self.set_position(pos) -- not shown
end

Now any reasonable Bus would also want to know what rotation it should have, because buses turn corners and go up and down hills, but we’re leaving that detail out for now: in essence we’d ask the path for that info as well.

The thing is, for purposes of Bus motion, this is really all the Bus wants to know about: given my desired distance along the path, what is the world position of that distance?

There might be other matters that the Bus wants to think about, like where to stop and what sign to display on the front, but those matters do not concern the Bus’s motion. So is this object best called “Bus”? Maybe not. Maybe it is “MovingVehicle”. I don’t know. It is way too soon to decide finally, and we should be prepared to rename things often as we learn more about them.

What about the path?

We now know something about the path: we know one of its methods:

-- sketch, not compiled or tested
Path = class()
function Path:init()
   -- to be figured out
   -- knows the path in some form
end

function Path:position_at_distance(distance)
   -- return a vector position along the path
end

To a first approximation this is all there is to a path: it knows a path and can return a position along it, given a distance.

Much of our job in creating these vehicles comes down to providing a format for the path that is easy enough to create and that can be processed fast enough by the Path code. We want to move our vehicles in tiny steps very fast, so our path code needs to be short and sweet. For most predefined paths, we can do as much processing as we want to get a form we like. The SLRR is an exception, as is any guide-based path: we can’t really pre-calculate the path.

But we have a first cut at two objects, Bus and Path. Complications will arise but first, let’s figure out at least one kind of Path. Let’s call it a FixedStepPath.

A FixedStepPath knows actual points along the path, at some fixed distance apart, say one meter just to keep it easy. So the path is a lot of short one-meter lines leading wherever you want. If the path is 100 meters around, the FixedStepPath has a big array of points 1 meter apart along the path.

-- sketch, not compiled or tested
FixedStepPath = class()
function FixedStepPath:init()
   self.path = self:getBigArrayFromSomewhere()
   self.len = #self.path
end

function FixedStepPath:position_at_distance(distance)
   -- return a vector position along the path
   local d_int, d_frac = self:normalize(distance)
   local pos_low = self.path[d_int]
   local pos_high = self.path[d_int+1]
   -- lerp
   return pos_high*d_frac + pos_low*(1-d_frac)
end

There is some hand-waving going on with normalize. I don’t mean that in the sense of normalizing a vector, just the notion that if distance is outside the range 0-99, we need to get it inside that range, and we need its integer and fraction parts so that we can interpolate between the two points surrounding distance. I am supposing that normalize returns an integer part 1-100, not 0-99, for compatibility with Lua arrays.

What Have We Here?

I think that what we have here is a fairly close sketch of the Lua code it would take to move a Bus around any fixed path represented by points separated by one meter, without rotating it to align with the road. Assume a spherical bus …

As an aside, if somewhere in there there were two entries ten meters apart instead of one, the Bus would treat that segment as if it were one meter and would get from one end of it to the other in one second, if we were traveling at a nominal one meter per second. The fixed length between points is important if we are to maintain a speed controlled by the vehicle by incrementing its distance. If the Path lies about the distance, that’s not good.

The Point Is ???

The point is that by thinking in terms of even these two very rudimentary objects, we get a separation of concerns that makes both objects pretty simple and quite useful. The Bus just thinks in terms of distance providing a position (and rotation when we get to it) and the Path just thinks in terms of translating a distance to a position on the path.

We can create any kind of vehicle so long as it thinks in terms of distance, and any kind of path so long as it can convert distance to position in a linear fashion, one meter of position change for one meter of distance.

It Can’t Be That Simple !!!

You’re right, it can’t. But it can be very simple compared to what we have to do in LSL, because Lua enables us to keep separate concerns separate and to plug in different objects so long as they obey the rules.

We’ll need some kind of GuideFollowingPath, for example. Can we create one? Perhaps, or perhaps we’ll have to modify the relationship between Vehicle and Path a bit. One idea would be to have the Path return a new distance as well as a position, when you ask for the position at distance d. What??

Well, note that there is an implicit agreement between Vehicle and Path about “distance”. The Vehicle expects to be able to increment (or decrement) distance and get positions near the ones it has. The path needs to understand those distances in terms of the kind of Path it is.

So an SLRR path doesn’t have a beginning and an end. One possibility with them would just be to increment forever. We could run a train for billions of meters before the integer would wrap around. But another possibility is for the Path to contain the recent past of travel and the near future of travel, a sort of sliding window on the infinite tracks of SLRR.

When the Path discovers some new path ahead of the train, it wants to put that in front of the train and in the interest of not running out of memory, it wants to forget some of the path already traveled. So we might want to treat distance zero as back at the new beginning of the end. So we could say to the Vehicle: what you are calling distance 10 is now distance 4. The Vehicle would just save that in its self.distance variable and carry on.

Just an idea. Because we keep the interface between Vehicle and Path as simple and abstract as possible, we can identify areas where the interface isn’t quite strong enough, and devise simple ways to make it work for both sides.

Enough (For Now)

Those are my Lua Vehicle Design Thoughts for Tuesday. They have been useful for me, and if anyone else ever reads this, I hope it’ll be useful for them as well. You. I hope it’s useful to you.

Safe paths!