I’m used to writing programs that consist of a fair number of object “classes”, with multiple objects, messages between them, and the like. I’ve written programs in procedural languages, using lots of small functions. Most RL programming languages provide some way of managing the program in pieces. In LSL, I found myself writing programs that were one big piece of paper. That gets pretty complicated, and it’s possible to do much better … sometimes.
Separate Functions are Easy in Real Life Languages …
I don’t program much in Java or C#, but I have done enough to know that there are some pretty easy conventions to follow, at least up to the size of program we’re likely to get in LSL. In those RL languages, each class is a separate text module. And using a testing tool like JUnit or NUnit, you can have tests for your code, separate from the code itself. Up to a pretty good-sized program, this breakout works well, and the tools automatically put things back together. The code you write is pretty much the same whether the code you’re using is in the same module or a separate one.
… but Hard in Second Life’s LSL
In LSL this just isn’t the case. If you have a function in the same source module with you, you can just call it:
integer max = getMaximumValue(listOfValues);
Howver, if you want to put some capability into a separate source script, you have to call it very differently. If it is in the same prim with your script you can use the llMessageLinked() function and the link_message event. This is very cryptic, because all you can pass through a link message is an integer and a string. What you usually do is use the integer to signal what is to be done, or maybe just to signal which script should listen, and you pack all the parameters, and possibly the desired action, into the string. This turns what should be a simple call into a major production. In addition, it takes time. You can only do 100 or so link messages in a second.
If the function you want to call is in a separate prim, it gets even worse. All you can really do is send a string message to the other script, using llSay() on some agreed channel. The other script listens, and when it hears a message, it does whatever has been asked. If it can do everything needed on its own, that’s as bad as it gets. If it has to return a result, it has to use llSay() again, to send the answer back!
Both these approaches are troublesome. They take a fair amount of code just to make the call, and often the calling program has to enter a special state to wait for an answer, which complicates things a lot. All this to do something that should really be as simple as calling a function. Arrgh. Sounds like something you should never do … but it isn’t.
As a program in LSL grows, its complexity grows disproportionately. (This is true in any language, but because LSL is so simplistic, it seems to happen faster.) Complexity is the enemy in programming. Once the program gets too complex for our minds to encompass, we can only fit parts of it in our head at a time. The more complex it gets, the more we are likely to make mistakes. As Yoda may have put it
Complexity is the path to the dark side. Complexity leads to confusion. Confusion leads to mistakes. Mistakes lead to suffering. — Yoda, private communication.
As a program in LSL grows, it gets harder and harder to perceive its “shape”. Writing code “by intention” helps with this, but as the program grows, programming by intention is not enough. And finally, if the program gets big enough, LSL runs out of capacity and the program is too big to run at all.
All these things have to be brought into balance. We need to manage the complexity our mind sees, the time the program needs to run, the size it can be. All this adds up to good reasons to use llMessageLinked() s or llSay() sometimes.
Valkyrie Transport Example
In the VTrans system there are two ways a vehicle can know its path. It can seek out the path for itself, as with the track-following trams, or it can be given the path in a notecard, as with the carts and balloons in our home in Lexicolo. The vehicles break down their paths into segments that can be straight lines, arcs, or even bezier curves. Putting all that stuff together into one vehicle script got just too complex for my small mind. So I broke the scripts into bits. One part figures out the path segments one way or another, and there are scripts I call “flyers”, each of which knows how to follow one kind of path segment. These scripts communicate using link_message. Once I got it set up, it wasn’t bad at all … and then things started to get easier!
Tiny Modules Pay Off!
We decided that we wanted to control the speed of the tram. Sure, easy enough, let’s have little speed limit signs. How should they work? Well, they are separate objects, so either the vehicles have to look for them, or they have to look for the vehicles. We decided on the latter. The sign runs a sensor and when it sees a vehicle, it uses llSay() to tell that vehicle the legal speed limit.
But how should the vehicle respond? It could be in any function anywhere. And there are two or more scripts that may need to respond immediately to the speed change … and those scripts are busy following path segments. What to do?
We decided to add a new little script to the vehicle that just listens for speed limit signs. When it hears a new limit, it checks to see if it applies to this vehicle, and if it does, it issues an llMessageLinked() message. Any script that needs to be sensitive to speed gets that message, and tucks the speed value away for use. If it runs later, fine, it’ll know the speed when the time comes. If it’s running now, that’s fine, too: the next time it moves along the path, it’ll move at the new speed right then.
This change made speed changing easy.
But Wait! There’s More!
Then Dizzi realized that when the vehicles changed speed, their wheel rotation needed to change too, so that the vehicle wouldn’t look like it was skidding. No problem! When the speed message is called, the wheels listen to the message as well, and update their rotation speed.
It turns out that once we made the investment to split the scripts up, it began to pay off by letting us write very simple small scripts, instead of editing features into scripts that get more and more complex, and harder to keep working.
Not a Conclusion … but a Possibility
I put off using the llMessageLinked() and llSay() to talk between scripts for a long time. Now that I’ve learned how to use them, it’s not so difficult, and I’ve learned that using these tools to break my scripts into smaller modules can pay off. As I go forward, I’ll probably try using these capabilities sooner … until I learn how soon is too soon!
That’s how I learn … bouncing from one mistake to another. Keeps me on the road, but makes sure that I get to learn.