Friday 24 August 2012

LSL tutorial: Communication between parent and child


Back when I started to write complex scripts, one of the first puzzles I needed to solve was this: Object A rezzes object B out of its inventory, and needs to send some startup information to B. How can it do this?

When A rezzes B, the only information it can pass to it in the call to llRezObject is a single integer. When object B wakes up, its on_rez event receives that integer as the start_param. But suppose you need to send more information than that? Then A has to send it by encoding it into a string and speaking it. The channel it is going to speak on can be sent as the start parameter.

This won’t work:

In A:
llRezObject( channelNumber, ... );
llSay( channelNumber, ... );

In B:
on_rez( integer channelNumber ) {
   llListen( channelNumber, ... );
}

The reason is that A cannot assume that B is listening, or even exists, by the time that A speaks. Spoken messages are never queued: if no-one is listening, they are not heard. How can A make sure that when it speaks to B, B is already rezzed and listening?

Adding a delay with llSleep won’t work either. There’s no guarantee that whatever delay you choose will be long enough, and when there is no lag, the delay will be unnecessary.

Another solution that does not work is for A to put the llSay into its object_rez event, which it will receive when B has been rezzed. This is still too soon. B is guaranteed to exist by this point, but it may not yet have called llListen.

The only way that A can get a guarantee that B is listening, is for B to send a signal to A after B starts listening. B can do this by speaking on the channel number it received. But how can A be sure to hear that message? By starting to listen on that channel before it ever creates B.

So a working protocol looks like this. This uses channelNumber for the channel on which A speaks to B, and channelNumber+1 for B speaking to A. It would work to use the same channel for both, but I prefer to use separate channels for different lines of communication.

In A:

rez_B() {
    llListen( channelNumber+1, ... );  // Start listening to B.
    llRezObject( channelNumber, ... ); // Create B.
}

listen(...)
    // Receive B's hello message.
    if (msg=="Hi mom!") {
        llSay( channelNumber, ...data for B... );
    }
}

In B:

on_rez( integer channelNumber ) {
    // Start listening to A.
    llListen( channelNumber, ... );
    // Tell A I've been rezzed.
    llSay( channelNumber+1, "Hi mom!" );
}

listen(...) {
    // process A's data.
}

The sequence of events will run like this:
  1. A starts listening to B.
  2. A rezzes B.
  3. B starts listening to A.
  4. B says hello to A.
  5. A receives hello from B.
  6. A sends data to B.
  7. B receives data from A.
This guarantees that every message is already being listened for before it is sent, no matter how much lag there is (as long as it’s not so much that messages are actually getting lost, but there’s not much you can do about that). And when lag is low, no time is lost in unnecessary waiting.

Finally, it would make sense to use a random channel number for this, e.g. chosen at random from numbers in the range –2000000000 to –1000000000. This will prevent accidental crosstalk between different objects carrying out the same protocol.

If A and B don’t need to talk to each other after that, then the listens should be removed. If they do, then the listens should be replaced by listens that are limited to the uuid of the other object, on the general principle that the more limited the listen, the less resources it uses. By this point, each of them has the uuid of the other, because that was transmitted in the messages they already passed.

I’ve made a full-perms demo of this, which I might put up on the Marketplace for free, but I don’t at the moment have an account there. Just ask if you want a copy.

No comments:

Post a Comment