Difference between revisions of "LSL To Client Communication"
Dale Glass (talk | contribs) (More details, example script) |
Dale Glass (talk | contribs) (formatting changes) |
||
Line 35: | Line 35: | ||
* DATA: Data being sent. The format of the data isn't specified. | * DATA: Data being sent. The format of the data isn't specified. | ||
==Behavior== | |||
This is how the current implementation handles the protocol: | This is how the current implementation handles the protocol: | ||
Line 53: | Line 53: | ||
If all those requirements are satisfied, the data in the message is sent to the specified part of the viewer for futher processing. | If all those requirements are satisfied, the data in the message is sent to the specified part of the viewer for futher processing. | ||
==Example== | |||
<pre><nowiki> | <pre><nowiki> | ||
Line 59: | Line 59: | ||
</nowiki></pre> | </nowiki></pre> | ||
==Implementation== | |||
Code for this can be found at [http://svn.daleglass.net/secondlife/trunk/indra/newview/llviewermessage.cpp], line 2047 | Code for this can be found at [http://svn.daleglass.net/secondlife/trunk/indra/newview/llviewermessage.cpp], line 2047 | ||
Note that at the time of writing, the client isn't capable of telling the difference between object speech sent to all users (llSay/etc) and object speech sent only to the owner (llOwnerSay). It also doesn't check the speaking object's key or its owner's. This code must be fixed to obtain that information, so that the viewer can ignore messages from objects not owned by the user, when such a thing is necessary. | Note that at the time of writing, the client isn't capable of telling the difference between object speech sent to all users (llSay/etc) and object speech sent only to the owner (llOwnerSay). It also doesn't check the speaking object's key or its owner's. This code must be fixed to obtain that information, so that the viewer can ignore messages from objects not owned by the user, when such a thing is necessary. | ||
=Protocol= | |||
The above describes the system and format for exchanging data. However, there are still a few problems to solve: | The above describes the system and format for exchanging data. However, there are still a few problems to solve: | ||
Line 77: | Line 77: | ||
# During the establishment of the connection, the script chooses a random channel to listen on, and tells it to the viewer. This makes it very improbable that somebody will be able to intercept the data, as the channel will be different every time. An additional advantage is that there's no need to keep track of who uses what channel: the chance of a collision should be extremely low. | # During the establishment of the connection, the script chooses a random channel to listen on, and tells it to the viewer. This makes it very improbable that somebody will be able to intercept the data, as the channel will be different every time. An additional advantage is that there's no need to keep track of who uses what channel: the chance of a collision should be extremely low. | ||
== Component Naming == | |||
The COMPONENT field of the protocol specifies the name of the component inside the viewer the script wants to talk to. The data is an arbitrary ASCII string, which may not contain newlines or the $ character. The proposed format is "AvatarName.FunctionName", where AvatarName is the first and last names of the avatar who created the functionality concatenated without a space, a period, and the name of the avatar's creation. This system should ensure that should two people come up with different ways to do the same thing, the COMPONENT names won't clash. | The COMPONENT field of the protocol specifies the name of the component inside the viewer the script wants to talk to. The data is an arbitrary ASCII string, which may not contain newlines or the $ character. The proposed format is "AvatarName.FunctionName", where AvatarName is the first and last names of the avatar who created the functionality concatenated without a space, a period, and the name of the avatar's creation. This system should ensure that should two people come up with different ways to do the same thing, the COMPONENT names won't clash. | ||
Line 85: | Line 85: | ||
</nowiki></pre> | </nowiki></pre> | ||
== Connection Establishment == | |||
As described above, the LSL script initiates the communication by telling the viewer what channel it wants to talk on. | As described above, the LSL script initiates the communication by telling the viewer what channel it wants to talk on. | ||
=== Connection Request === | |||
* The COMPONENT field's value is "DaleGlass.Viewer" | * The COMPONENT field's value is "DaleGlass.Viewer" | ||
* The payload is "Connect$<channel>" | * The payload is "Connect$<channel>" | ||
Line 100: | Line 100: | ||
</nowiki></pre> | </nowiki></pre> | ||
=== Viewer Reply === | |||
Viewer replies to the request by sending the message "OK" to the specified channel. Any other reply MUST be considered an error message. | Viewer replies to the request by sending the message "OK" to the specified channel. Any other reply MUST be considered an error message. | ||
Revision as of 16:57, 22 June 2007
Communication
A mechanism is needed to allow LSL scripts to talk to the viewer in order to allow implementing some interesting functionality, such as viewer UI extensions that communicate with in-world objects, for instance. Viewer to LSL communication is easily possible, by making the viewer talk on a specific channel, or use XML RPC. This may not be ideal for all cases, but it's already doable without any modifications.
What needs work is LSL to Client communication, as there currently isn't any good method in the official client for that.
Requirements
A robust mechanism should satisfy these requirements:
- Scripts must be able to receive messages from the viewer (can be done already with chat and XML RPC)
- Scripts must be able to reply to the viewer
- It must be possible to communicate with multiple viewers at once without confusion
- Order of delivery must be guaranteed (chat messages currently can be reordered)
- Messages must be reliable
The hack below can satisfy requirements #1 to #3. Requirement #4 would require extra work (although it's doable) and is possibly best ignored, as LL mentioned the possibility of moving chat to TCP, which would automatically solve the issue.
Requirement #5 is mostly satisfied as data isn't lost, and the only problem may be with the viewer or the script disappearing, which can be dealt with by designing the proper protocol, if needed.
Hack
While there's no official solution, here's what I came up with for now:
- Client to LSL: Client speaks on a channel directly. Nothing special here.
- LSL to Client: Script uses llOwnerSay, with this format:
$VwrComm$VERSION$COMPONENT$DATA
Fields:
- VwrComm: Header, must be present
- VERSION: Protocol version
- COMPONENT: Component inside the viewer the message is for
- DATA: Data being sent. The format of the data isn't specified.
Behavior
This is how the current implementation handles the protocol:
- Message must come from an object. Normal chat doesn't trigger it
- String gets split by "$" and must have at least 4 tokens
- First token must be VwrComm
- Second token must be "0" (version number, this will change)
- Third token matches the name of a known component.
If requirements #1 to #3 aren't satisfied, the string is passed as-is, and the code doesn't suppress its display.
If requirement #4 isn't satisfied, the text won't appear on screen, but the code won't continue further either. A message will be logged in the viewer's log, saying that a message with the wrong version was received.
If requirement #5 isn't satisfied, display is also suppressed, and a message is also logged.
If all those requirements are satisfied, the data in the message is sent to the specified part of the viewer for futher processing.
Example
$VwrComm$0$DaleGlass.AvatarScanner$LoginComplete
Implementation
Code for this can be found at [1], line 2047
Note that at the time of writing, the client isn't capable of telling the difference between object speech sent to all users (llSay/etc) and object speech sent only to the owner (llOwnerSay). It also doesn't check the speaking object's key or its owner's. This code must be fixed to obtain that information, so that the viewer can ignore messages from objects not owned by the user, when such a thing is necessary.
Protocol
The above describes the system and format for exchanging data. However, there are still a few problems to solve:
- Multiple extensions to the viewer must be able to coexist, without conflicting with others
- Viewer may not implement this at all, in which case the messages get displayed on screen
- Viewer replies to the script have potential security issues
Proposed solutions:
- See next section
- The LSL script initiates the conversation. It sends a single request and waits for a reply. This ensures that non-supporting clients don't get flooded with protocol data.
- During the establishment of the connection, the script chooses a random channel to listen on, and tells it to the viewer. This makes it very improbable that somebody will be able to intercept the data, as the channel will be different every time. An additional advantage is that there's no need to keep track of who uses what channel: the chance of a collision should be extremely low.
Component Naming
The COMPONENT field of the protocol specifies the name of the component inside the viewer the script wants to talk to. The data is an arbitrary ASCII string, which may not contain newlines or the $ character. The proposed format is "AvatarName.FunctionName", where AvatarName is the first and last names of the avatar who created the functionality concatenated without a space, a period, and the name of the avatar's creation. This system should ensure that should two people come up with different ways to do the same thing, the COMPONENT names won't clash.
Example:
$VwrComm$0$DaleGlass.AvatarScanner$LoginComplete
Connection Establishment
As described above, the LSL script initiates the communication by telling the viewer what channel it wants to talk on.
Connection Request
- The COMPONENT field's value is "DaleGlass.Viewer"
- The payload is "Connect$<channel>"
The fields are as follows:
- <channel> is the channel the script wants to talk on. For security, this SHOULD be randomly generated.
Example:
$VwrComm$0$DaleGlass.Viewer$Connect$12421
Viewer Reply
Viewer replies to the request by sending the message "OK" to the specified channel. Any other reply MUST be considered an error message.
After this, any further requests to the viewer from the object are replied to in on the specified channel.
Example
integer g_channel = 0; // Channel to talk on -- randomly generated integer g_listen_handle = 0; // Listen handle integer g_timeout = 30; // How long to wait for the viewer to reply ViewerComm(string component, string data) { llOwnerSay("$VwrComm$0$" + component + "$" + data); } default { state_entry() { // Generate a random channel number g_channel = (integer)(llFRand(4294967295) - 2147483648); // Set a timer in case the viewer doesn't reply llSetTimerEvent(g_timeout); // Listen for replies g_listen_handle = llListen(g_channel, "", llGetOwner(), ""); // Send connection request ViewerComm("DaleGlass.Viewer", "Connect$" + (string)channel); } timer() { llListenRemove(g_listen_handle); llOwnerSay("Client/Viewer connection timed out -- probably not supported"); } listen( integer channel, string name, key id, string message ) { if ( message == "OK" ) { // Connection successful llOwnerSay("Client/Viewer connection successful"); } else { // Something went wrong llOwnerSay("Client/Viewer connection error: " + message); } } }