Difference between revisions of "LSL Protocol/rfedip"

From Second Life Wiki
Jump to navigation Jump to search
m (fixed code tags and whitespace and intra-wiki links, please add linebreaks to provided scripts and maybe break the page into sections)
(article rewritten)
Line 1: Line 1:
==Ratany fine Engineerings Device Interface Protocol (rfedip)==
==Ratany fine Engineerings Device Interface Protocol (rfedip)==


===About===
The Ratany fine Engineering Device Interface Protocol (short: rfedip) is an extensible protocol for the detection of compliant devices and for the handling of the communication between such devices.


The Ratany fine Engineering Device Interface Protocol (short: rfedip) is an extensible protocol for the detection of compliant devices and for the handling of the communication between such devices.
The protocol defines '''how''' devices can be detected and rules for '''how''' the devices can communicate.  It does '''not''' define the very content of such communications, with the exception of a few so-called control messages.


The protocol defines '''how''' devices can be detected and rules for '''how''' the devices can communicate. It does '''not''' define the very content of such communications, with the exception of a few so-called control messages.


The protocol has been created because there doesn´t seem to be any sort of standard to detect devices. Devices can be leashing posts, furniture, collars, cuffs and chastity belts as well as any other gadget the design of which involves interoperability or some sort of communication with other devices.
The protocol has been created because there doesn´t seem to be any sort of standard to detect devices. Devices can be leashing posts, furniture, collars, cuffs and chastity belts as well as any other gadget the design of which involves interoperability or some sort of communication with other devices.


Using sensors to scan for devices has turned out to be an insufficient alternative. The sensor range is inevitably limited, and particular prims --- which may be the actual points of an object to tie a leash to --- of objects cannot be detected with a sensor.
Using sensors to scan for devices has turned out to be an insufficient alternative. The sensor range is inevitably limited, and particular prims --- which may be the actual points of an object to tie a leash to --- of objects cannot be detected with a sensor.


Using existing protocols like the lockmeister protocol also turned out not to be feasible. A device using the lockmeister protocol only becomes active from an agent sitting on the device or touching the device, and the device does itself not listen for queries to identify itself.
Using existing protocols like the lockmeister protocol also turned out not to be feasible. A device using the lockmeister protocol only becomes active from an agent sitting on the device or touching the device, and the device does itself not listen for queries to identify itself.


At the time of this writing, open collar items seem to employ some sort of protocol and maybe even some sort of API. However, these are not documented, and development appears to be in progress such that significant changes might be implemented.
At the time of this writing, open collar items seem to employ some sort of protocol and maybe even some sort of API. However, these are not documented, and development appears to be in progress such that significant changes might be implemented.


Other protocols may exist that are vendor-specific. Insofar their designs have not been disclosed, they are unsuited to achieve compatibility and interoperability of devices made by different creators. A device that lacks compatibility with other devices may have no more than a very limited use for the user of the device, especially when the functionality of the device genuinely requires that the device works with others.
Other protocols may exist that are vendor-specific. Insofar their designs have not been disclosed, they are unsuited to achieve compatibility and interoperability of devices made by different creators. A device that lacks compatibility with other devices may have no more than a very limited use for the user of the device, especially when the functionality of the device genuinely requires that the device works with others.


The Ratany fine Engineering Device Interface Protocol is intended to encourage and to help with the creation of devices that are compatible with others.
The Ratany fine Engineering Device Interface Protocol is intended to encourage and to help with the creation of devices that are compatible with others.


The availability of a useful protocol does not mean that creators of devices will be inclined to use it. Please consider this article as a draft with the intention to create a useful protocol which hopefully might be used by many creators --- and as a request for comments. Please feel free to use the discussion page of this article to add your suggestions and ideas, or to contact the author directly.


===Requirements brought upon a protocol===
The availability of a useful protocol does not mean that creators of devices will be inclined to use it.  Please consider this article as a draft with the intention to create a useful protocol which hopefully might be used by many creators --- and as a request for comments. Please feel free to use the discussion page of this article to add your suggestions and ideas, or to contact me directly.
 
==Editing this Article==
 
This article is written in my favourite editor.  When I update the article on the wiki, I edit the whole article, delete everything and paste the new version in.  That´s simply the easiest and most efficient way for me.
 
Unfortunately, this means that your modifications may be lost when I update the article and don´t see them.
 
'''Please use the "discussion" page of this article to post contributions or to suggest changes so they can be added to the article.'''
 
==Requirements brought upon a protocol==


The protocol should be:
The protocol should be:


* available
* available
Line 38: Line 47:




===Specification===
==Specification==


====In General====
===In General===


The rfedip protcol uses strings of characters that can be transfered as messages of a type which can be sent by functions like [[llMessageLinked]], [[llRegionSayTo]], [[llRegionSay]], [[llShout]], [[llSay]], [[llWhisper]] as well as in the body of an email or via the use of the HTTP protocol.
The rfedip protcol uses strings of characters that can be transfered as messages of a type which can be sent by functions like llMessageLinked(), llRegionSayTo(), llRegionSay(), llShout(), llSay(), llWhisper() as well as in the body of an email or via the use of the HTTP protocol.


An rfedip message is a [[String|string]] which is divided into fields by the use of a special character as separator. The fields of a message are defined as follows, and they appear in the message in the order they are listed here:
An rfedip message is a string which is divided into fields by the use of a special character as separator. The fields of a message are defined as follows, and they appear in the message in the order they are listed here:


* sender-UUID: The [[UUID]] of the sending device.


* recipient-UUID: The [[UUID]] of the device the message is directed to.
* sender-UUID: The UUID of the sending device.


* protocol version: A [[String|string]] that specifies the version of the protocol.
* recipient-UUID: The UUID of the device the message is directed to.


* payload: The payload of the protocol. The payload is generally assumed to consist of a token. The token may be followed by a parameter. Multiple tokens and parameters may be sent in a single message. However, a single message should not contain multiple tokens. Multiple messages should be used to avoid the creation of messages that contain multiple tokens.
* protocol version: A string that specifies the version of the protocol.


All devices compliant with the rfedip protocol must have available documentation about all the tokens and, if applicable, the parameters used with the tokens the device supports. The documentation must include the tokens and parameters the device understands, as well as the tokens and parameters it might use itself to communicate with other rfedip-compliant devices.
* payload: The payload of the protocol.  The payload is generally assumed to consist of a token. The token may be followed by a parameter.  Multiple tokens and parameters may be sent in a single message.  However, a single message should not contain multiple tokens.  Multiple messages should be used to avoid the creation of messages that contain multiple tokens.
 
 
All devices compliant with the rfedip protocol must have available documentation about all the tokens and, if applicable, the parameters used with the tokens the device supports. The documentation must include the tokens and parameters the device understands, as well as the tokens and parameters it might use itself to communicate with other rfedip-compliant devices.


This is to ensure that creators and users of rfedip-compliant devices can refer to the documentation of devices created by others and make the devices they are creating compatible with devices created by others, if they so choose.
This is to ensure that creators and users of rfedip-compliant devices can refer to the documentation of devices created by others and make the devices they are creating compatible with devices created by others, if they so choose.
Line 60: Line 71:
A device for which such documentation is not freely available cannot be considered as compliant or compatible with the rfedip protocol.
A device for which such documentation is not freely available cannot be considered as compliant or compatible with the rfedip protocol.


====Definitions====
 
===Definitions===


The rfedip protocol itself defines:
The rfedip protocol itself defines:


* a [[String|string]] specifying the protocol version: "RFEDIP-1.0"
 
* a string specifying the protocol version: "RFEDIP-1.0"


* that future versions of the protocol must maintain compatibility with previous versions of the protocol
* that future versions of the protocol must maintain compatibility with previous versions of the protocol
Line 70: Line 83:
* a suggestion as to what may be considered as "sufficient version": "RFEDIP" (What is considered as "sufficient version" for a particular device is up to the creator of that device.)
* a suggestion as to what may be considered as "sufficient version": "RFEDIP" (What is considered as "sufficient version" for a particular device is up to the creator of that device.)


* the special character that must be used to seperate a [[String|string]] of characters into fields in order to form a protocol message: "|"
* the special character that must be used to seperate a string of characters into fields in order to form a protocol message: "|"


* a default communications channel, when applicable: -20131224
* a default communications channel, when applicable: -20131224
Line 95: Line 108:




===Example Implementation===
==Reference Implementation: A Tethering Device==


====First Example: A device providing chaining points====
The tethering device can be queried for the UUIDs of prims to which chains or leashes can be attached.  The token to query the device is "tether".  The device replies with the token "tether point", followed by the UUID of a chaining point.  Each chaining point is reported in a seperate response message, one chaining point per message.  When all chaining points have been reported, this devices sends a RFEDIP_protEND control message.


For an example implementation, a simple rfedip-compliant device will be created here that allows another device to query the [[UUID|UUIDs]] of so-called chain points of an object.
Since the author is using C++ as preprocessor for LSL scripts (and other tools), the actual source code for the device will be provided first. The LSL script that is the result of the compilation process will be given below.  Even without full syntax highlighting, the source is IMO much easier to read than the actual script.
You do not need to use C++ or other tools to make use of the rfedip-protocol.  How you actually implement the protocol is completely up to you, this is merely an example.
To create the device, a generic template is used.  It´s called a generic template because it doesn´t contain more than what is needed to answer identification requests and to check whether a received message appears to be a valid rfedip-protocol message.  For anything beyond that, a function (named rfedip_handle()) is called.  (This has the advantage that the function can be defined in a script for a particular device which simply includes the template, and many different devices can be made which all use the same template.)
You can think of this template as a script that handles some control messages of the protocol which are not device-specific.
<cpp>
// Ratanay fine Engineering Device Interface Protocol
//
// generic example template
//
#include <lslstddef.h>
#include <rfedip.h>
default
{
    event state_entry()
    {
        // permanently listen on the protocol channel
        //
        llListen(RFEDIP_CHANNEL, "", NULL_KEY, "");
    }
    event listen(int channel, string name, key other_device, string _MESSAGE)
    {
        IfMessage(RFEDIP_protIDENTIFY_QUERY)
        {
            // Indistinctively answer queries that want to
            // detect this device: The answer goes to the
            // sender (i. e. other_device) and looks like:
            //
            // "<sender-uuid>|<recipient-uuid>|RFEDIP_sVERSION|RFEDIP_protIDENTIFY"
            //
            // For the device that receives the answer to
            // the request to identify, the <sender-uuid>
            // is the UUID of this device.
            //
            // With
            //
            //  RFEDIP_RESPOND(<recipient-uuid>, <sender-uuid>, <protocol-payload>);
            //
            // an answer is sent to the device from which
            // this device has received the request to
            // identify itself:
            //
#define this_device  llGetLinkKey(llGetLinkNumber())  // the UUID of this device
            RFEDIP_RESPOND(other_device, this_device, RFEDIP_CHANNEL, RFEDIP_protIDENTIFY);
            return;
        }
        // From here on, received messages are expected to
        // look like:
        //
        // "<sender-uuid>|<recipient-uuid>|RFEDIP_sVERSION|<token>[|parameter|parameter...]"
        //
        // <sender-uuid> is the UUID of the device sending the
        // message (i. e. other_device); <recipient-uuid> is
        // the UUID of the recipient, i. e. of this device
        //
        // Please do not confuse incoming messages with
        // outgoing messages!
        //
        // Parameters can be tokens.  However, this is not
        // recommended.  Send multiple messages rather than
        // multiple tokens in one message.
        //
        // The received message is put into a list for further
        // processing:
        //
        list payload = ProtocolData(RFEDIP_sSEP);
        // Attempt to verify whether the message looks valid:
        //
        if(Len(payload) < RFEDIP_iMINMSGLEN) return;
        // Attempt to make sure that the message is for this
        // device:
        //
        // + ignore messages that appear not to be sent by the
        //  device they claim to be sent from by verifying
        //  the sender given in the message with the actual
        //  sender:
        //
#define InvalidSender (RFEDIP_ToSENDER(payload) != other_device)
        //
        // + ignore messages that appear not be destined for
        //  this device by verifying the recipient given in
        //  the message with what this device actually is:
        //
#define NotDestined  (RFEDIP_ToRCPT(payload) != this_device)
        //
        // + ignore messages that request a different version
        //  of the protocol by verifying the protocol version
        //  given in the message --- in this example, a check
        //  for what is considered a "sufficient version" is
        //  applied:
        //
#define BadVersion    !Instr(RFEDIP_ToPROTVERSION(payload), RFEDIP_sSUFFICIENT_VERSION)
        //
        when(InvalidSender || NotDestined || BadVersion) return;
#undef this_device
#undef InvalidSender
#undef NotDestined
#undef BadVersion
        // From here on, the capabilities of the device can be
        // implemented.
        //
        // Since this is a template, just call a handling
        // function so the template can simply be included
        // into a script that implements such capabilities.
        //
        rfedip_handle(payload);
    }
}
</cpp>
As you can see, above template uses <code>#includes</code> the two files <code>lslstddef.h</code> and <code>rfedip.h</code>.  Providing <code>lslstddef.h</code>, and how to use C++ as a preprocessor, is far outside the scope of this article.  Only <code>rfedip.h</code> is required here:
<cpp>
// defines for Ratany fine Engineerings Device Interface Protocol
#ifndef _RFEDIP
#define _RFEDIP
#define RFEDIP_sVERSION                          "RFEDIP-1.0"  // protocol version
#ifndef RFEDIP_sSUFFICIENT_VERSION
#define RFEDIP_sSUFFICIENT_VERSION                "RFEDIP"  // default to any version as sufficient version
#endif
#define RFEDIP_sSEP                              "|"  // separator used in protocol messages
#define RFEDIP_CHANNEL                            -20131224  // default channel used for protocol messages
#define RFEDIP_protIDENTIFY_QUERY                "identify"  // identification query, must be responded to with RFEDIP_protIDENTIFY
#define RFEDIP_protIDENTIFY                      "identity"  // answer to identification queries
#define RFEDIP_protEND                            "msg_end!"  // indicate end of data transfer; if a particular channel was opened, this channel can be closed
#define RFEDIP_protOPEN                          "openchan"  // open a particular channel for further communication, mandatorily has a channel number as parameter
#define RFEDIP_ToSENDER(_l)                      llList2Key(_l, 0)  // return UUID of sender from list _l
#define RFEDIP_ToRCPT(_l)                        llList2Key(_l, 1)  // return UUID of recipient from list _l
#define RFEDIP_ToPROTVERSION(_l)                  llList2String(_l, 2) // return string containing the version of the protocol from list _l
#define RFEDIP_ToFirstTOKEN(_l)                  llList2String(_l, 3)  // return the first token from list _l
#define RFEDIP_ToFirstPARAM(_l)                  llList2String(_l, 4) // return the first parameter
#define RFEDIP_ToRESPONSE(_sndr, _rcpt, ...)      llDumpList2String([_sndr, _rcpt, RFEDIP_sVERSION, __VA_ARGS__], RFEDIP_sSEP)  // convert a protocol payload into a protocol message
#define REFDIP_OPEN(_rcpt, _sndr, _nchan)        llRegionSayTo(_rcpt, RFEDIP_CHANNEL, RFEDIP_ToRESPONSE(_sndr, _rcpt, RFEDIP_protOPEN, _nchan))  // open a particular channel for communication
#define RFEDIP_END(_rcpt, _sndr, _nchan)          llRegionSayTo(_rcpt, RFEDIP_CHANNEL, RFEDIP_ToRESPONSE(_sndr, _rcpt, RFEDIP_protEND, _nchan))  // indicate end of communication on channel _c
#define RFEDIP_IDQUERY                            llRegionSay(RFEDIP_CHANNEL, RFEDIP_protIDENTIFY_QUERY)  // ask all rfedip compliant devices to identify themselves
#define RFEDIP_RESPOND(_rcpt, _sndr, _chan, ...)  llRegionSayTo(_rcpt, _chan, RFEDIP_ToRESPONSE(_sndr, _rcpt, __VA_ARGS__))
#define RFEDIP_iMINMSGLEN                        4  // used to figure out whether a message is a RfE-dip message or not
#endif  // _RFEDIP
</cpp>
To create the actual device for this example, the script that provides the function which handles the device-specific protocol messages is also needed.  It simply defines the function to report the chain points on request:
<cpp>
// RfE-dip compatible generic chaining device
//
// "Generic" means that all prims of the object (i. e. device) this
// script is in which are named sCHAINPOINT (i. e. "hook", see below)
// will be reported via the Ratany fine Engineering device interface
// protocol when a compatible device queries them.  In case there are
// no prims with that name and the script is in the root prim, the
// root prim will be reported.
// some standard definitions
//
#include <lslstddef.h>
// use the getlinknumbersbyname() function from a library that
// provides functions to deal with names and descriptions of prims in
// order to figure out link numbers
//
#define _USE_getlinknumbersbyname
#include <getlinknumbers.lsl>
// some standard definitions for rfedip:
//
#include <rfedip.h>
// some definitions needed by devices that communicate with this
// device
//
#include <rfedip-generic-chainpoints.h>
// the prims of the device that are points to attach chains to are
// named "hook", unless otherwise defined
#ifndef sCHAINPOINT
#define sCHAINPOINT                "hook"
#endif
// When a request for chaining points has been received, respond with
// the UUID of each chaining point of this device.  Once all the
// available points are reported, the the RFEDIP_protEND token is sent
// to indicate that the data transfer is complete.
//
// Report one chaining point per message to simplify parsing for the
// receiver and to avoid problems with maximum message length.
//
// rfedip_handle() is called from the RfE-dip-template.  In this case,
// no more than a few lines are needed which can easily be inlined.
// (Inlining can save memory compared to actually using a function and
// may run faster.)
//
// When receiving messages not handled by this device, indicate end of
// communication to potentially save other devices unnecessary waiting
// times.
//
#define rfedip_handle(_data)                        \
    when(RFEDIP_ToFirstTOKEN(_data) == protTETHER)          \
    {                              \
        list hooks = getlinknumbersbyname(sCHAINPOINT);    \
        key sender = RFEDIP_ToSENDER(_data);            \
        key this = llGetLinkKey(llGetLinkNumber());    \
        if(!Len(hooks) && (llGetLinkNumber() < 2)) hooks = (list)llGetLinkNumber(); \
        int n = Len(hooks);                \
        LoopDown(n, RFEDIP_RESPOND(sender, this, RFEDIP_CHANNEL, protTETHER_RESPONSE, llGetLinkKey(llList2Integer(hooks, n)))); \
        RFEDIP_END(sender, this, RFEDIP_CHANNEL);      \
    }                              \
    else RFEDIP_END(RFEDIP_ToSENDER(_data), llGetLinkKey(llGetLinkNumber()), RFEDIP_CHANNEL);
// Simply use the RfE-dip template as is.
//
// For devices that consist of many prims, it is better to use a
// global variable for the list of hooks which isn´t updated on every
// query.  With such devices, use the template directly here rather
// than including it, and adjust it accordingly.  For the purpose of
// this example, things are kept simple.
//
#include <rfedip-template.lsl>
</cpp>
''''Note:'''' Devices should respond with the protocol message indicating the end of communication for tokens they do not understand to potentially save other devices unnesessary waiting times. The generic template relies on the function that implements the device-specific capabilities to do this, and the function in this devices does it.
As you can see, <code>rfedip-generic-chainpoints.h</code> is included. The definitions are in a header file so other devices can use them as well:
<cpp>
// device specific protocol payload; for this device, this is
// intentionally very similar to lockmeister
//
// The device specific protocol payload is what must be documented for
// each device to be compatible.  In this case, for example:
//
// "The device can be queried for the UUIDs of prims to which chains
// can be attached.  The token to query the device is "tether".  The
// device replies with the token "tether point", followed by the UUID
// of a chaining point.  Each chaining point is reported in a seperate
// response message, one chaining point per message.  Once all points
// have been reported, the device signals end of communication. ..."
//
#define protTETHER                "tether"
#define protTETHER_RESPONSE        "tether point"
</cpp>
The compilation process delivers the LSL script which you put into the object that becomes an rfedip-compliant device. Of course, when you do not use cpp and other tools, you would write such a script "directly":


<lsl>
<lsl>
list getlinknumbersbyname(string name)
list getlinknumbersbyname(string name)
{
{
            name    = llToLower(name);
integer n = llGetObjectPrimCount(llGetLinkKey(llGetLinkNumber()));
    integer n       = llGetObjectPrimCount(llGetLinkKey(llGetLinkNumber()));
list numbers = [];
    list   numbers = [];
name = llToLower(name);


    if(n > 1)
if(n > 1)
    {
{
        while(n)
while(n)
        {
{
            string linkName = llList2String(llGetLinkPrimitiveParams(n, [PRIM_NAME]), 0);
if((~llSubStringIndex(llToLower(llList2String(llGetLinkPrimitiveParams(
            if((~llSubStringIndex(llToLower(linkName), name)))
                                    n, [PRIM_NAME]), 0)), name)))
            {
{
                numbers += n;
numbers += n;
            }
}


            --n;
--n;
        }
}
    }
}
    else
else
    {
{
        if((~llSubStringIndex(llToLower(llGetObjectName() ), name)))
if((~llSubStringIndex(llToLower(llGetObjectName()), name)))
        {
{
            numbers += n;
numbers += n;
        }
}
    }
}


    return numbers;
return numbers;
}
}


default
key kThisDevice;
{
list lHooks;
    state_entry()
    {
        llListen(-20131224, "", NULL_KEY, "");
    }
 
    listen(integer channel, string name, key other_device, string _MESSAGE)
    {
        if("identify" == _MESSAGE)
        {
            llRegionSayTo(other_device, -20131224, llDumpList2String([llGetLinkKey(llGetLinkNumber()), other_device, "RFEDIP-1.0", "identity"], "|"));
            return;
        }
 
        list payload = llParseString2List((_MESSAGE), ["|"], []);
 
        if(llGetListLength(payload) < 4)
            return;
 
        if((llList2Key(payload, 0) != other_device)
            || (llList2Key(payload, 1) != llGetLinkKey(llGetLinkNumber()))
            || !(~llSubStringIndex(llList2String(payload, 2), "RFEDIP")))
            return;
 
        if(llList2String(payload, 3) == "tether")
        {
            list hooks  = getlinknumbersbyname("hook");
            key sender = llList2Key(payload, 0);
            key  this  = llGetLinkKey(llGetLinkNumber());
 
            if(!llGetListLength(hooks) && (llGetLinkNumber() < 2))
                hooks = (list)llGetLinkNumber();
 
            integer n = llGetListLength(hooks);


            while(n)
            {
                --n;
                llRegionSayTo(sender, -20131224, llDumpList2String([this, sender, "RFEDIP-1.0", "tether point", llGetLinkKey(llList2Integer(hooks, n))], "|"));
            }
            llRegionSayTo(sender, -20131224, llDumpList2String([this, sender, "RFEDIP-1.0", "msg_end!", -20131224], "|"));
        }
        else
            llRegionSayTo(llList2Key(payload, 0), -20131224, llDumpList2String([llGetLinkKey(llGetLinkNumber()), llList2Key(payload, 0), "RFEDIP-1.0", "msg_end!", -20131224], "|"));
    }
}
</lsl>
'''Notes:'''
* This device does not work when worn as an attachment, see [[llGetObjectPrimCount]].
* Do not use this script for an actual device. It is inefficient to create the list of tether points each time when the device is queried. Adjust the script for your actual device accordingly. Please see the second exaple for how to do this, and check out the reference implementation.
====Second Example: A device using chaining points====
The second example is a device that queries the device in the first example for the chaining points and uses those. You can use this device to automatically tie up your boat or your airship in it´s berth, or to tie an agent to some piece of furniture. You may be building (flying) vehicles, tie a trailer or some to them with some chains and have the trailer(s) follow the vehicle as if towed on these chains. Use it for whatever comes to mind.
This device was chosen for an example because it fits to the device from the first example, and because it seemed really easy to attach a few chains to a few chain points in such a way that the result doesn´t look retarded. It´s ridiculously simple to do, and humans do it like every day without thinking about it at all. Therefore, a small, simple script, well suited for an example, could certainly do that.
This train of thought has been derailed. Though the algorithm is simple once understood, it isn´t very small and was surprisingly hard to find. It would be interesting to see a better one, especially one that scales better with the number of chains used.
This example starts with the template from the first example, using a modified version of it. Things have been added to the template because this device requires them, and the template is not #included but used "directly".
To point out the part which is more relevant for the example, functions and definitions have been put into a separate file which is #included.
<cpp>
// Ratany fine Engineering Interface Device Protocol example
//
// implementation of a device that queries another rfedip device for
// chaining points and then makes use of the points.
//
// Use this device to tie up your boat or something :)
// include the stuff that isn´t entirely relevant for the purpose of
// this example and needed for this device --- this is only so to make
// things easier to read
//
#include <rfedip-second-example-header.h>
// This is a version of the template modified for this particular
// device.
//
default
default
{
{
    event listen(int channel, string name, key other_device, string _MESSAGE)
state_entry()
    {
{
        // identify this device
// remember the link numbers of the chain targets
        //
//
        IfMessage(RFEDIP_protIDENTIFY_QUERY)
lHooks = getlinknumbersbyname("hook");
        {
            RFEDIP_RESPOND(other_device, llGetLinkKey(llGetLinkNumber()), RFEDIP_CHANNEL, RFEDIP_protIDENTIFY);
            return;
        }


        // verify whether the received message looks like a
if(!(llGetListLength(lHooks)))
        // valid refdip-protocol message
{
        //
lHooks = (list)llGetLinkNumber();
        list payload = ProtocolData(RFEDIP_sSEP);
}
        if(Len(payload) < RFEDIP_iMINMSGLEN) return;
        when((RFEDIP_ToSENDER(payload) != other_device) || (RFEDIP_ToRCPT(payload) != llGetLinkKey(llGetLinkNumber())) || !Instr(RFEDIP_ToPROTVERSION(payload), RFEDIP_sSUFFICIENT_VERSION)) return;


        // extract the token from the rfedip message ...
kThisDevice = llGetLinkKey(llGetLinkNumber());
        //
llListen(-20131224, "", NULL_KEY, "");
        string token = RFEDIP_ToFirstTOKEN(payload);
}


        //
changed(integer w)
        // ... to do some device specific stuff
{
        //
// maybe do something else here if you need to sit on
        // The order of the message handling has been arranged
// this device
        // for better performance.
//
        //
if(w & CHANGED_LINK)
{
kThisDevice = llGetLinkKey(llGetLinkNumber());
lHooks = getlinknumbersbyname("hook");


        when(protTETHER_RESPONSE == token)
if(!(llGetListLength(lHooks)))
            {
{
                // THIRD: Receive the responses to the query for points to attach chains to
lHooks = (list)llGetLinkNumber();
                //        and remember those points.
}
                //
}
                Enlist(lTargets, RFEDIP_ToFirstPARAM(payload));
}
                return;
            }


        when(RFEDIP_protEND == token)
listen(integer channel, string name, key other_device, string _MESSAGE)
            {
{
                IfStatus(stRECEIVING)
if("identify" == _MESSAGE)
                {
{
                    // Receiving an end-message means that the device which sent it has finished
// Indistinctively answer queries that want to
                    // answering.
// detect this device: The answer goes to the
                    //
// sender (i. e. other_device) and looks like:
                    --iDevicesAround;
//
                    iDevicesAround = Max(iDevicesAround, 0); // ignore unexpected end-msgs
// "<sender-uuid>|<recipient-uuid>|RFEDIP_sVERSION|RFEDIP_protIDENTIFY"
//
// For the device that receives the answer to
// the request to identify, the <sender-uuid>
// is the UUID of this device.
//
llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice,
              other_device, "RFEDIP-1.0", "identity"], "|"));
return;
}


                    // FOURTH: When all answers have been received, make the chains.
// From here on, received messages are expected to
                    //         In case an end-message gets lost, the timeout kicks in.
// look like:
                    //
//
                    unless(iDevicesAround) mkchains();
// "<sender-uuid>|<recipient-uuid>|RFEDIP_sVERSION|<token>[|parameter|parameter...]"
                }
//
// <sender-uuid> is the UUID of the device sending the
// message (i. e. other_device); <recipient-uuid> is
// the UUID of the recipient, i. e. of this device
//
// Please do not confuse incoming messages with
// outgoing messages!


                return;
// The received message is put into a list for further
            }
// processing:
//
list payload = llParseString2List((_MESSAGE), ["|"], []);


        when(RFEDIP_protIDENTIFY == token)
// Attempt to verify whether the message looks valid:
            {
//
                // SECOND: Ask a device that identifies itself for points to attach chains to.
if(llGetListLength(payload) < 4)
                //
{
                ++iDevicesAround;
return;
                RFEDIP_RESPOND(other_device, llGetLinkKey(llGetLinkNumber()), RFEDIP_CHANNEL, protTETHER);
}
                return;
            }


        // when receiving messages not handled by this device,
// Attempt to make sure that the message is for this
        // indicate end of communication to potentially save
// device:
        // other devices unnecessary waiting times
//
        //
// + ignore messages that appear not to be sent by the
        RFEDIP_END(other_device, llGetLinkKey(llGetLinkNumber()), RFEDIP_CHANNEL);
//  device they claim to be sent from by verifying
    }
//  the sender given in the message with the actual
//  sender
//
// + ignore messages that appear not be destined for
//  this device by verifying the recipient given in
//  the message with what this device actually is
//
// + ignore messages that request a different version
//  of the protocol by verifying the protocol version
//  given in the message --- in this example, a check
//   for what is considered a "sufficient version" is
//   applied
//
if((llList2Key(payload, 0) != other_device)
  || (llList2Key(payload, 1) != kThisDevice)
  || !(~llSubStringIndex(llList2String(payload, 2), "RFEDIP")))
{
return;
}


    event touch_start(int t)
// From here on, the capabilities of the device can be
    {
// implemented.
        unless(Len(lChains))
            {
                aftell("no chains to create");
                return;
            }


        IfStatus(stBUSY)
// Here: Report the chain target points and send of of
        {
// communication message.
            aftell("busy, please wait");
//
            return;
if(llList2String(payload, 3) == "tether")
        }
{
integer n = llGetListLength(lHooks);


        SetStatus(stBUSY);
while(n)
        LoopChains(xChainHide(_n); yChainUnlink(_n));
{
        aftell("querying for devices");
--n;
        SoundPing;
llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice,
        lTargets = [];
              other_device, "RFEDIP-1.0", "tether point", llGetLinkKey(llList2Integer(lHooks,
                      n))], "|"));
}
}


        // keep track of how many devices are detected to be
// indicate end of communication when all points have
        // able to decide whether all answers from all devices
// been reported or when messages have been received
        // have been received or not
// this device doesn´t support
        //
//
        iDevicesAround = 0;
llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice,
              other_device, "RFEDIP-1.0", "msg_end!", -20131224], "|"));
}


        // there isn´t a way around a timeout with this device :(
on_rez(integer p)
        //
{
        llSetTimerEvent(fTIMER_UNWAIT);
// UUID changes when rezzed
 
//
        // FIRST: query for rfedip-compliant devices
kThisDevice = llGetLinkKey(llGetLinkNumber());
        //
}
        RFEDIP_IDQUERY;
        SetStatus(stRECEIVING);
    }
 
    event timer()
    {
        // A message indicating end of communication might not
        // have arrived.  Go ahead with whatever information
        // has been received so far.
        //
        aftell("timeout");
        mkchains();
    }
 
    event state_entry()
    {
        ClrStatus;
        yChainsInit;
 
#if DEBUG
        LoopChains(SLPPF(iChainLinkNo(_n), [PRIM_TEXT, sChainName(_n), GREEN, 1.0]));
#else
        LoopChains(SLPPF(iChainLinkNo(_n), [PRIM_TEXT, "", BLACK, 0.0]));
#endif
 
        llListen(RFEDIP_CHANNEL, "", NULL_KEY, "");
    }
 
    event changed(int w)
    {
        when(w & CHANGED_LINK)
            {
                // maybe do something else here if you need to sit on this device
                //
                LoopChains(xChainHide(_n); yChainUnlink(_n));
                yChainsInit;
            }
    }
}
}
</cpp>
</lsl>
 
This is basically it. The listener event handles the rfedip-protocol messages and acts upon the tokens. If a token is not understood by this devices, it sends an end-of-communication message. Most of what is in the other events is specific for this device.
 
The functionality of this device is implemented in what´s in <code>rfedip-second-example-header.h</code>. It´s a bit lengthy, and you can safely skip this part:
 
<cpp>
// header file for rfedip-example device
//
// Contains stuff that isn´t entirely relevant for the purpose of the
// example.
 
 
// some standard definitions
//
#include <lslstddef.h>
#include <colordef.h>
 
// use the getlinknumbersbylistnamedappend_attached() function from a
// library that provides functions to deal with names and descriptions
// of prims in order to figure out link numbers
//
#define _USE_getlinknumbersbylistnamedappend_attached
#define _GETLINKNUMBERSBYLISTNAMEDAPPEND_ATTACHED_APPENDIX [NULL_KEY]
#include <getlinknumbers.lsl>
 
// use the geom_linecrosspoint() function from a library that provides
// functions that deal with geometry
//
#define _GEOM_USE_geom_linesegmentscross
#include <geometry.lsl>
//
// this is defined to easily switch the very function used
//
#define boolChainsAreCrossing(_v1, _v2, _v3, _v4) geom_linesegmentscross(_v1, _v2, _v3, _v4)
 
// some standard definitions for rfedip:
//
#include <rfedip.h>
 
// some definitions from devices that communicate with this
// device
//
#include <rfedip-generic-chainpoints.h>
 
 
// The standard library (lslstddef.h) provides status handling, which
// requires an integer to store the status´ in:
//
int status;
#define stBUSY                    1
#define stRECEIVING                2
 
 
// This particular device can have multiple chains, created from the
// prims with names listed here.  Add as many chains as you like,
// until the script runs out of memory.
//
#define lCHAINTOS                  ["Chain-0", "Chain-1", "Chain-2", "Chain-3", "Chain-4", "Chain-5", "Chain-6", "Chain-7", "Chain-8", "Chain-9", "Chain-10", "Chain-11"]
 
 
int iDevicesAround;                    // keep track of how many other devices have been found
#define iSTRIDE_lChains            3    // lChains is a strided list: [prim name, link number, uuid of target]
#define iINDEXOFFSET_lChains      2    // needed to deduct which chain is linked to a given target point
list lChains;                          // lChains is a strided list: [prim name, link number, uuid of target], see notes below
list lTargets;                          // a list of uuids of prims to leash to, as reported via rfedip
 
// a few macro definitions to deal with the chains
//
// Consider them as a demonstration of the incredible usefulness of a
// preprocessor.
//
#define boolChainEndsAboveOrBelow(_venda, _vendb) ((_venda.x == _vendb.x) && (_venda.y == _vendb.y) && (_venda.z != _vendb.z))  // see notes below
#define boolChainLinked(_n)        (kChainKey(_n) != NULL_KEY)
#define fChainLength(_c)          llVecDist(vChainPos(_c), vChainEndpos(_c))
#define iChainLinkNo(_n)          llList2Integer(lChains, (_n) + 1)
#define kChainKey(_n)              llList2Key(lChains, (_n) + 2)
#define sChainName(_n)            llList2String(lChains, _n)
#define vChainEndpos(_c)          RemotePos(kChainKey(_c))
#define vChainPos(_chain)          (llGetRootPosition() + llList2Vector(GLPP(iChainLinkNo(_chain), [PRIM_POS_LOCAL]), 0) * llGetRootRotation())
#define xChainHide(_n)            llLinkParticleSystem(iChainLinkNo(_n), [])
#define xChainShow(_n, _vcolour)  llLinkParticleSystem(iChainLinkNo(_n), CHAINS_lPARTICLE_CHAIN(_n, _vcolour))
#define yChainLink(_n, _k)        (lChains = llListReplaceList(lChains, (list)_k, (_n) + 2, (_n) + 2))
#define yChainUnlink(_n)          (lChains = llListReplaceList(lChains, (list)NULL_KEY, (_n) + 2, (_n) + 2))
#define yChainsInit                (lChains = getlinknumbersbylistnamedappend_attached(lCHAINTOS))
 
#define LoopChains(_do)            int _n = Len(lChains); while(_n) { _n -= iSTRIDE_lChains; _do; }
 
#define kTargetKey(_target)        llList2Key(lTargets, _target)
#define vTargetPos(_target)        RemotePos(kTargetKey(_target))
#define fDistChainTarget(_c, _t)  llVecDist(vChainPos(_c), vTargetPos(_t))
 
#define CHAINS_lPARTICLE_CHAIN(_n, _vc)  [                  \
                      PSYS_PART_MAX_AGE, 3.0,      \
                                      PSYS_PART_FLAGS, PSYS_PART_FOLLOW_VELOCITY_MASK | PSYS_PART_TARGET_POS_MASK | PSYS_PART_FOLLOW_SRC_MASK | PSYS_PART_EMISSIVE_MASK, \
                                      PSYS_PART_START_COLOR, _vc, \
                                      PSYS_PART_START_SCALE, <0.2, 0.2, 0.0>, \
                                      PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_DROP, \
                                      PSYS_SRC_BURST_RATE, 0.04,    \
                                      PSYS_SRC_ACCEL, <0.0, 0.0, -1.2>, \
                                      PSYS_SRC_BURST_PART_COUNT, 1, \
                                      PSYS_SRC_BURST_SPEED_MIN, 3.0,    \
                                      PSYS_SRC_BURST_SPEED_MAX, 3.0,    \
                                      PSYS_SRC_TARGET_KEY, kChainKey(_n), \
                                      PSYS_SRC_MAX_AGE, 0.0,        \
                                      PSYS_SRC_TEXTURE, "4cde01ac-4279-2742-71e1-47ff81cc3529" \
                    ]
 
// unfortunately, this device needs a timer because messages can be
// lost, or no compliant devices might be around
//
#define fTIMER_UNWAIT              30.0
 
// sometimes stuff is undetermined
//
#define fUNDETERMINED              -1.0
#define iUNDETERMINED              -1
#define vUNDETERMINED              (<fUNDETERMINED, fUNDETERMINED, fUNDETERMINED>)
#define fIsUndetermined(_f)        (fUNDETERMINED == _f)
#define iIsUndetermined(_i)        (iUNDETERMINED == _i)
#define vIsUndetermined(_v)        (vUNDETERMINED == _v)
 
 
#define DEBUG 0
 
 
//
// Notes:
//
// You can save memory by not storing the prim names in lChains.  The
// prim names are irrelevant here for anything but figuring out the
// link numbers and for debugging-messages.
//
// boolChainEndsAboveOrBelow() is to handle a special case.  The
// function to determine whether two line segments cross or not
// considers two dimensions only (x and y coordinates).  This makes
// sense because it is somewhat unlikely for two chains to cross each
// other exactly when three dimensions (x, y and z coordinates) are
// considered.
//
// The special case is that the end points of two chains may have the
// same x and y coordinates while they have different z
// coordinates. This happens when two target points are above each
// other.  The chains would be considered as crossing each other (at
// their end points), which is not desirable for this application.
// boolChainEndsAboveOrBelow() is used to detect this special case and
// to make the chains considered as not crossing each other.
//
 
 
// figure out which chain is attached to a target point
//
// This should be a macro, yet with the ternary operator missing from
// the script language, it´s not reasonable to implement it as a
// macro here.
//
int iChainOfTarget(int t)
{
    int x = LstIdx(lChains, kTargetKey(t));
    if(~x) return (x - iINDEXOFFSET_lChains);
 
    return iUNDETERMINED;
}
 
 
// figure out which chain target is closest to a given chain point
//
int closest_target(int chain)
{
    int ret = iUNDETERMINED;
    vector chainpos = vChainPos(chain);
    float closest = fUNDETERMINED;
    int n = Len(lTargets);
    while(n)
        {
            --n;
 
            float distance = llVecDist(vTargetPos(n), chainpos);
            if(fIsUndetermined(closest))
                {
                    closest = distance;
                    ret = n;
                }
            else
                {
                    if(distance < closest)
                        {
                            closest = distance;
                            ret = n;
                        }
                }
        }
 
    return ret;
}
 
 
// compute the average position of all the chain targets
//
vector avgpos_targets()
{
    vector avg = vUNDETERMINED;
    int cnt = 0;
    int t = Len(lTargets);
    while(t)
        {
            --t;
 
            avg += vTargetPos(t);
            ++cnt;
        }
 
    return (avg / (float)cnt);
}
 
 
// figure out which chain point is the furthest away from a given position
//
int furthest_chain(vector ref)
{
    float distmax = fUNDETERMINED;
    int ret = iUNDETERMINED;
    int c = Len(lChains);
    while(c)
        {
            c -= iSTRIDE_lChains;
 
            unless(boolChainLinked(c))
                {
                    float dist = llVecDist(vChainPos(c), ref);
                    if(distmax < dist)
                        {
                            distmax = dist;
                            ret = c;
                        }
                }
        }
 
    return ret;
}
 
 
// swap the target points of two chains
//
void swap_chains(int ca, int cb)
{
    key tmp = kChainKey(ca);
 
    yChainLink(ca, kChainKey(cb));
    yChainLink(cb, tmp);
}
 
 
// figure out how many chains cross a given chain and pick one that
// crosses it
//
list find_chain_crossedby(int chain)
{
    vector start = vChainPos(chain);
    vector end = vChainEndpos(chain);
 
    int many = 0;
    int which = iUNDETERMINED;
 
    int c = Len(lChains);
    while(c)
        {
            c -= iSTRIDE_lChains;
 
            when((chain != c) && boolChainLinked(c))
                {
                    vector cend = vChainEndpos(c);
                    unless(boolChainEndsAboveOrBelow(end, cend))
                        {
                            if(boolChainsAreCrossing(start, end, vChainPos(c), cend))
                                {
                                    DEBUGmsg(sChainName(c) + " crosses " + sChainName(chain));
                                    ++many;
                                    which = c;
                                }
                        }
                }
        }


    return [which, many];
}


You can find the source for this device in the [https://github.com/Ratany/lsl-repo git repository] and further documentation in this [[How_to_make_writing_LSL_scripts_easier|article]].


// figure out which chain point is closest to a given target point
//
int closest_chain(int target)
{
    vector targetpos = vTargetPos(target);
    float distance = fUNDETERMINED;
    int ret = iUNDETERMINED;


    int c = Len(lChains);
    while(c)
        {
            c -= iSTRIDE_lChains;


            unless(boolChainLinked(c))
==Reference Implementation: A device using chaining points==
                {
                    if(fIsUndetermined(distance))
                        {
                            distance = llVecDist(targetpos, vChainPos(c));
                            ret = c;
                        }
                    else
                        {
                            float f = llVecDist(targetpos, vChainPos(c));
                            if(distance > f)
                                {
                                    distance = f;
                                    ret = c;
                                }
                        }
                }
        }


    return ret;
This device can send queries for the UUIDs of prims to which chains can be attached.  The token used for the query is "tether".  This device expects answers to this query to use the token "tether point", followed by the UUID of the point as parameter.  It expects devices that have received a query to indicate that all their UUUIDs have been reported by sending a a RFEDIP_protEND control message, regardless whether any points have been reported or not.
}


This device can be queried for the UUIDs of prims to which chains can be attached.  The token to query the device is "tether".  The device replies with the token "tether point", followed by the UUID of a chaining point.  Each chaining point is reported in a seperate response message, one chaining point per message.  When all chaining points have been reported, this devices sends a RFEDIP_protEND control message.


// Make a chain from each chain point to the chain target closest to
When this device has not created chains itself, it does not report its chain target points but sends a RFEDIP_protEND control message instead.
// that chain point, no more than one chain per target.  It would be
// really interesting to see a better algorithm for this.
//
// caveats: It is undetermined whether a solution can be found in all
//          possible cases or not.
//
//          The algorithm scales horribly.
//
void mkchains()
{
    UnStatus(stRECEIVING);
    llSetTimerEvent(0.0);
    aftell("computing ...");


    // Furthest-first chain-linking may yield results that look
    // retarded when there are less targets than there are
    // chains.  Link closest-first instead.
    //
    when(Len(lTargets) < Len(lChains) / iSTRIDE_lChains)
        {
            // link to closest target


            int t = Len(lTargets);
The implementation happened to grow quite a bit larger than what I had expected.  What I expected is that it is simple to link a couple chains from an object to a couple points that are somewhere around.  Pfff!
            while(t)
                {
                    --t;


                    int c = closest_chain(t);
It is simple to link each chain to the point closest to it.  When you do that, the result looks retarded in most cases because the chains may get entangled or go across the object, and several chains may go to the same point.  That was ridiculous and not acceptable.
                    unless(iIsUndetermined(c)) yChainLink(c, kTargetKey(t));
                }
        }
    else
        {
            // link furthest chain first


            vector ref = avgpos_targets();
Humans solve the problem without thinking.  For example, they tie their boats to the tethering points around the berths their boats are in without entangling all the ropes.  They do not attach the ropes in such a way that they go across the boat.  Humans are flexible and sometimes cross over some ropes or have multiple ropes go to the same point when the conditions let this appear to be advisable.  The result never looks retarded because the way it´s done makes sense.
            unless(vIsUndetermined(ref))
                {
                    int notbreak = Len(lTargets);
                    int c = furthest_chain(ref);
                    while(notbreak && !iIsUndetermined(c))
                        {
                            int t = closest_target(c);
                            unless(iIsUndetermined(t))
                                {
                                    yChainLink(c, kTargetKey(t));
                                    lTargets = llDeleteSubList(lTargets, t, t);
                                    --notbreak;
                                }
                            c = furthest_chain(ref);
                        }
                }


            // Removing targets a chain was linked to from their list has
Even children do it right.  I couldn´t figure out how I would do it because I would just do it.  People I asked didn´t know how to do it, either. The problem is too simpleThis made it surprisingly difficult to create an algorithm which yields results that don´t look retarded for at least most conditions.  It took a lot of experimentation and was fun to create.
            // avoided linking multiple chains to the same target.
            //
            // Since the targets must still be known, their list is
            // re-created by looking at the chainsTargets that don´t
            // have a chain linked to them are forgotten.
            //
            // In case memory runs out while re-enlisting
            // the targets, the result may look retarded.
            //
            lTargets = [];
            LoopChains(when(boolChainLinked(_n)) Enlist(lTargets, kChainKey(_n)));
        }


    int tries = Len(lChains); // in case it won´t solve, don´t do forever
What this algorithm does, and how it does it, is irrelevant for the purpose of this article. I´ll only put the more relevant part into the article.  You can find the whole source in the [https://github.com/Ratany/lsl-repo git repository] and further documentation in this [[How_to_make_writing_LSL_scripts_easier|article]].
    bool swapped;
    do  // remember bubblesort?
        {
            swapped = FALSE;


            int c = Len(lChains);
            while(c)
                {
                    c -= iSTRIDE_lChains;
                    unless(boolChainLinked(c)) continue next_chain;
                    // disentangle the chains: Swap this chain with another chain that
                    // crosses this chain, unless swapping the chains entangles all of
                    // them more.
                    //
// make the list returned by find_chain_crossedby() useable:
//
#define xWhich(_l) llList2Integer(_l, 0)
#define xMany(_l)  llList2Integer(_l, 1)
//
                    list crosses = find_chain_crossedby(c);
                    unless(iIsUndetermined(xWhich(crosses)))
                        {
                            DEBUGmsg("swap: " + sChainName(c) + " and " + sChainName(xWhich(crosses)) + ", " + (string)xMany(crosses) + " xings");
                            swap_chains(c, xWhich(crosses));
                            swapped = TRUE;
                            list more = find_chain_crossedby(c);
                            if(!iIsUndetermined(xWhich(more)) && (xMany(crosses) < xMany(more)))
                                {
                                    DEBUGmsg("unswap: " + sChainName(c) + " and " + sChainName(xWhich(crosses)) + ", " + (string)xMany(crosses) + " xings vs. " + (string)xMany(more));
                                    swap_chains(c, xWhich(crosses));
                                    swapped = FALSE;
                                }
#if DEBUG
                            else
                                {
                                    DEBUGmsg("no unswap");
                                }
#endif
                        }
#undef xMany
#undef xWhich
                    // disentangle the chains even more: See if there is a target-point
                    // closer to the chain-point of this chain which has an alternative
                    // chain linked to it, and swap this chain and the alternative chain
                    // when this chain and the alternative chain are crossing each other.
                    //
                    // (Swapping chains that do not cross each other can make them cross
                    // each other, and they might be swapped back (by above), effectively
                    // leading to swapping them back and forth indefinitely.)
                    //
                    int alternative_target = closest_target(c);
                    unless(iIsUndetermined(alternative_target))
                        {
                            if(fDistChainTarget(c, alternative_target) < fChainLength(c))
                                {
                                    int alternative_chain = iChainOfTarget(alternative_target);
                                    unless(iIsUndetermined(alternative_chain))
                                        {
                                            vector endpos_c = vChainEndpos(c);
                                            vector endpos_alternative_chain = vTargetPos(alternative_target);
                                            unless(boolChainEndsAboveOrBelow(endpos_c, endpos_alternative_chain))
                                                {
                                                    when(boolChainsAreCrossing(vChainPos(c), endpos_c, vChainPos(alternative_chain), endpos_alternative_chain))
                                                        {
                                                            swap_chains(c, alternative_chain);
                                                            swapped = TRUE;
                                                        }
                                                }
                                        }
                                    else aftell("data mismatch at " + __FILE__ + ":" + (string)__LINE__);
                                }
                        }
                    @next_chain;
                }
            --tries;
        }
    while(swapped && tries);
    // when the chains are linked and (hopefully) disentangled,
    // show the result:
    if(tries) aftell("solution found");
#if DEBUG
    LoopChains(when(boolChainLinked(_n)) { vector color = RandomColor; xChainShow(_n, color); SLPPF(iChainLinkNo(_n), [PRIM_TEXT, sChainName(_n), color, 1.0]); });
#else
    LoopChains(when(boolChainLinked(_n)) { vector color = RandomColor; xChainShow(_n, color); });
#endif
    UnStatus(stBUSY);
    aftell("done");
}
</cpp>
Note: This device could be worn as an attachment.
Except for <code>colordef.h</code> and <code>geometry.lsl</code>, the #included files are the same as with the first example.
Finally, here is the actual LSL script that results from the compilation:


<lsl>
<lsl>
list getlinknumbersbylistnamedappend_attached(list names)
{
    list    strided = [];
    integer n      = llGetNumberOfPrims();
    do
    {
        string thisname = llList2String(llGetLinkPrimitiveParams(n, [PRIM_NAME]), 0);
        if((~llListFindList(names, [thisname])))
        {
            strided += [thisname, n] + [NULL_KEY];
        }
        --n;
    }
    while(n > 0);
    return strided;
}
integer geom_linesegmentscross(vector p1, vector p2, vector p3, vector p4)
{
    vector a                = p2 - p1;
    vector b                = p3 - p4;
    vector c                = p1 - p3;
    float  alphaNumerator  = b.y*c.x - b.x*c.y;
    float  alphaDenominator = a.y*b.x - a.x*b.y;
    float  betaNumerator    = a.x*c.y - a.y*c.x;
    float  betaDenominator  = a.y*b.x - a.x*b.y;
    if ((alphaDenominator == 0.0) || (betaDenominator == 0.0))
        return FALSE;
    if (alphaDenominator > 0.0)
    {
        if ((alphaNumerator < 0.0) || (alphaNumerator > alphaDenominator))
            return FALSE;
    }
    else if ((alphaNumerator > 0.0) || (alphaNumerator < alphaDenominator))
        return FALSE;
    if (betaDenominator > 0.0)
    {
        if ((betaNumerator < 0.0) || (betaNumerator > betaDenominator))
            return FALSE;
    }
    else if ((betaNumerator > 0.0) || (betaNumerator < betaDenominator))
        return FALSE;
    return TRUE;
}
integer status;
integer iDevicesAround;
list    lChains;
list    lTargets;
integer iChainOfTarget(integer t)
{
    integer x = llListFindList(lChains, [llList2Key(lTargets, t)]);
    if (~x)
        return (x - 2);
    return -1;
}
integer closest_target(integer chain)
{
    integer ret      = -1;
    vector  chainpos = (llGetRootPosition() + llList2Vector(llGetLinkPrimitiveParams(llList2Integer(lChains, (chain) + 1), [PRIM_POS_LOCAL]), 0) * llGetRootRotation());
    float  closest  = -1.0;
    integer n        = llGetListLength(lTargets);
    while(n)
    {
        --n;
        float distance = llVecDist(llList2Vector(llGetObjectDetails(llList2Key(lTargets, n), [OBJECT_POS] ), 0), chainpos);
        if((-1.0 == closest))
        {
            closest = distance;
            ret = n;
        }
        else
        {
            if(distance < closest)
            {
                closest = distance;
                ret = n;
            }
        }
    }
    return ret;
}
vector avgpos_targets()
{
    vector  avg = ( < -1.0, -1.0, -1.0 > );
    integer cnt = 0;
    integer t  = llGetListLength(lTargets);
    while(t)
    {
        --t;
        avg += llList2Vector(llGetObjectDetails(llList2Key(lTargets, t), [OBJECT_POS] ), 0);
        ++cnt;
    }
    return (avg / (float)cnt);
}
integer furthest_chain(vector ref)
{
    float distmax = -1.0;
    integer ret = -1;
    integer c = llGetListLength(lChains);
    while(c)
    {
        c -= 3;
        if(!((llList2Key(lChains, (c) + 2) != NULL_KEY)))
        {
            float dist = llVecDist((llGetRootPosition() + llList2Vector(llGetLinkPrimitiveParams(llList2Integer(lChains, (c) + 1), [PRIM_POS_LOCAL]), 0) * llGetRootRotation()), ref);
            if(distmax < dist)
            {
                distmax = dist;
                ret = c;
            }
        }
    }
    return ret;
}
swap_chains(integer ca, integer cb)
{
    key tmp = llList2Key(lChains, (ca) + 2);
    (lChains = llListReplaceList(lChains, (list)llList2Key(lChains, (cb) + 2), (ca) + 2, (ca) + 2));
    (lChains = llListReplaceList(lChains, (list)tmp, (cb) + 2, (cb) + 2));
}
list find_chain_crossedby(integer chain)
{
    vector start = (llGetRootPosition() + llList2Vector(llGetLinkPrimitiveParams(llList2Integer(lChains, (chain) + 1), [PRIM_POS_LOCAL]), 0) * llGetRootRotation());
    vector end = llList2Vector(llGetObjectDetails(llList2Key(lChains, (chain) + 2), [OBJECT_POS] ), 0);
    integer many = 0;
    integer which = -1;
    integer c = llGetListLength(lChains);
    while(c)
    {
        c -= 3;
        if((chain != c) && (llList2Key(lChains, (c) + 2) != NULL_KEY))
        {
            vector cend = llList2Vector(llGetObjectDetails(llList2Key(lChains, (c) + 2), [OBJECT_POS] ), 0);
            if(!(((end.x == cend.x) && (end.y == cend.y) && (end.z != cend.z))))
            {
                if(geom_linesegmentscross(start, end, (llGetRootPosition() + llList2Vector(llGetLinkPrimitiveParams(llList2Integer(lChains, (c) + 1), [PRIM_POS_LOCAL]), 0) * llGetRootRotation()), cend))
                {
                    ;
                    ++many;
                    which = c;
                }
            }
        }
    }
    return [which, many];
}
integer closest_chain(integer target)
{
    vector targetpos = llList2Vector(llGetObjectDetails(llList2Key(lTargets, target), [OBJECT_POS] ), 0);
    float distance = -1.0;
    integer ret = -1;
    integer c = llGetListLength(lChains);
    while(c)
    {
        c -= 3;
        if(!((llList2Key(lChains, (c) + 2) != NULL_KEY)))
        {
            if((-1.0 == distance))
            {
                distance = llVecDist(targetpos, (llGetRootPosition() + llList2Vector(llGetLinkPrimitiveParams(llList2Integer(lChains, (c) + 1), [PRIM_POS_LOCAL]), 0) * llGetRootRotation()));
                ret = c;
            }
            else
            {
                float f = llVecDist(targetpos, (llGetRootPosition() + llList2Vector(llGetLinkPrimitiveParams(llList2Integer(lChains, (c) + 1), [PRIM_POS_LOCAL]), 0) * llGetRootRotation()));
                if(distance > f)
                {
                    distance = f;
                    ret = c;
                }
            }
        }
    }
    return ret;
}
mkchains()
{
    (status -= 2 * ( !!(status & 2) ) );
    llSetTimerEvent(0.0);
    llSay(0, "(" + (string)( (61440 - llGetUsedMemory() ) >> 10) + "kB) ~> " + "computing ...");
    if(llGetListLength(lTargets) < llGetListLength(lChains) / 3)
    {
        integer t = llGetListLength(lTargets);
        while(t)
        {
            --t;
            integer c = closest_chain(t);
            if(!((-1 == c))) (lChains = llListReplaceList(lChains, (list)llList2Key(lTargets, t), (c) + 2, (c) + 2));
        }
    }
    else
    {
        vector ref = avgpos_targets();
        if(!((( < -1.0, -1.0, -1.0 > ) == ref)))
        {
            integer notbreak = llGetListLength(lTargets);
            integer c = furthest_chain(ref);
            while(notbreak && !(-1 == c))
            {
                integer t = closest_target(c);
                if(!((-1 == t)))
                {
                    (lChains = llListReplaceList(lChains, (list)llList2Key(lTargets, t), (c) + 2, (c) + 2));
                    lTargets = llDeleteSubList(lTargets, t, t);
                    --notbreak;
                }
                c = furthest_chain(ref);
            }
        }
        lTargets = [];
        integer _n = llGetListLength(lChains);
        while(_n)
        {
            _n -= 3;
            if((llList2Key(lChains, (_n) + 2) != NULL_KEY)) if((llGetUsedMemory() < 61440) && !~llListFindList(lTargets, [llList2Key(lChains, (_n) + 2)])) lTargets += [llList2Key(lChains, (_n) + 2)];
        }
    }
    integer tries = llGetListLength(lChains);
    integer swapped;
    do
    {
        swapped = FALSE;
        integer c = llGetListLength(lChains);
        while(c)
        {
            c -= 3;
            if(!((llList2Key(lChains, (c) + 2) != NULL_KEY))) jump next_chain;
            list crosses = find_chain_crossedby(c);
            if(!((-1 == llList2Integer(crosses, 0))))
            {
                ;
                swap_chains(c, llList2Integer(crosses, 0));
                swapped = TRUE;
                list more = find_chain_crossedby(c);
                if(!(-1 == llList2Integer(more, 0)) && (llList2Integer(crosses, 1) < llList2Integer(more, 1)))
                {
                    ;
                    swap_chains(c, llList2Integer(crosses, 0));
                    swapped = FALSE;
                }
            }
            integer alternative_target = closest_target(c);
            if(!((-1 == alternative_target)))
            {
                if(llVecDist((llGetRootPosition() + llList2Vector(llGetLinkPrimitiveParams(llList2Integer(lChains, (c) + 1), [PRIM_POS_LOCAL]), 0) * llGetRootRotation()), llList2Vector(llGetObjectDetails(llList2Key(lTargets, alternative_target), [OBJECT_POS] ), 0)) < llVecDist((llGetRootPosition() + llList2Vector(llGetLinkPrimitiveParams(llList2Integer(lChains, (c) + 1), [PRIM_POS_LOCAL]), 0) * llGetRootRotation()), llList2Vector(llGetObjectDetails(llList2Key(lChains, (c) + 2), [OBJECT_POS] ), 0)))
                {
                    integer alternative_chain = iChainOfTarget(alternative_target);
                    if(!((-1 == alternative_chain)))
                    {
                        vector endpos_c = llList2Vector(llGetObjectDetails(llList2Key(lChains, (c) + 2), [OBJECT_POS] ), 0);
                        vector endpos_alternative_chain = llList2Vector(llGetObjectDetails(llList2Key(lTargets, alternative_target), [OBJECT_POS] ), 0);
                        if(!(((endpos_c.x == endpos_alternative_chain.x) && (endpos_c.y == endpos_alternative_chain.y) && (endpos_c.z != endpos_alternative_chain.z))))
                        {
                            if(geom_linesegmentscross((llGetRootPosition() + llList2Vector(llGetLinkPrimitiveParams(llList2Integer(lChains, (c) + 1), [PRIM_POS_LOCAL]), 0) * llGetRootRotation()), endpos_c, (llGetRootPosition() + llList2Vector(llGetLinkPrimitiveParams(llList2Integer(lChains, (alternative_chain) + 1), [PRIM_POS_LOCAL]), 0) * llGetRootRotation()), endpos_alternative_chain))
                            {
                                swap_chains(c, alternative_chain);
                                swapped = TRUE;
                            }
                        }
                    }
                    else llSay(0, "(" + (string)( (61440 - llGetUsedMemory() ) >> 10) + "kB) ~> " + "data mismatch at " + "./lib/rfedip-second-example-header.h" + ":" + (string)465);
                }
            }
            @next_chain;
        }
        --tries;
    }
    while(swapped && tries);
    if(tries) llSay(0, "(" + (string)( (61440 - llGetUsedMemory() ) >> 10) + "kB) ~> " + "solution found");
    integer _n = llGetListLength(lChains);
    while(_n)
    {
        _n -= 3;
        if((llList2Key(lChains, (_n) + 2) != NULL_KEY))
        {
            vector color = <llFrand(1.0), llFrand(1.0), llFrand(1.0)>;
            llLinkParticleSystem(llList2Integer(lChains, (_n) + 1), [ PSYS_PART_MAX_AGE, 3.0, PSYS_PART_FLAGS, PSYS_PART_FOLLOW_VELOCITY_MASK | PSYS_PART_TARGET_POS_MASK | PSYS_PART_FOLLOW_SRC_MASK | PSYS_PART_EMISSIVE_MASK, PSYS_PART_START_COLOR, color, PSYS_PART_START_SCALE, <0.2, 0.2, 0.0>, PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_DROP, PSYS_SRC_BURST_RATE, 0.04, PSYS_SRC_ACCEL, < 0.0, 0.0, -1.2 >, PSYS_SRC_BURST_PART_COUNT, 1, PSYS_SRC_BURST_SPEED_MIN, 3.0, PSYS_SRC_BURST_SPEED_MAX, 3.0, PSYS_SRC_TARGET_KEY, llList2Key(lChains, (_n) + 2), PSYS_SRC_MAX_AGE, 0.0, PSYS_SRC_TEXTURE, "4cde01ac-4279-2742-71e1-47ff81cc3529" ]);
        }
    }
    (status -= 1 * ( !!(status & 1) ) );
    llSay(0, "(" + (string)( (61440 - llGetUsedMemory() ) >> 10) + "kB) ~> " + "done");
}
default
default
{
{
    listen(integer channel, string name, key other_device, string _MESSAGE)
listen(integer channel, string name, key other_device, string _MESSAGE)
    {
{
        if("identify" == _MESSAGE)
//
        {
// This first part is the same as with the tethering device.
            llRegionSayTo(other_device, -20131224, llDumpList2String([llGetLinkKey(llGetLinkNumber()), other_device, "RFEDIP-1.0", "identity"], "|"));
//
            return;
        }


        list payload = llParseString2List((_MESSAGE), ["|"], []);
if("identify" == _MESSAGE)
{
llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice,
              other_device, "RFEDIP-1.0", "identity"], "|"));
return;
}


        if(llGetListLength(payload) < 4) return;
list payload = llParseString2List((_MESSAGE), ["|"], []);


        if((llList2Key(payload, 0) != other_device) || (llList2Key(payload, 1) != llGetLinkKey(llGetLinkNumber())) || !(~llSubStringIndex(llList2String(payload, 2), "RFEDIP") )) return;
if(llGetListLength(payload) < 4)
{
return;
}


        string token = llList2String(payload, 3);
if((llList2Key(payload, 0) != other_device)
  || (llList2Key(payload, 1) != kThisDevice)
  || !(~llSubStringIndex(llList2String(payload, 2), "RFEDIP")))
{
return;
}


        if("tether point" == token)
string token = llList2String(payload, 3);
        {
            if((llGetUsedMemory() < 61440) && !~llListFindList(lTargets, [llList2String(payload, 4)])) lTargets += [llList2String(payload, 4)];


            return;
//
        }
// The rest is device specific.  Tokens in received
// protocol messages trigger various actions to make
// the device to its job.
//


        if("msg_end!" == token)
if("tether point" == token)
        {
{
            if(( !!(status & 2) ) )
// [...]
            {
}
                --iDevicesAround;
                iDevicesAround = ( ( (llAbs( (iDevicesAround) >= (0) ) ) * (iDevicesAround) ) + ( (llAbs( (iDevicesAround) < (0) ) ) * (0) ) );


                if(!(iDevicesAround)) mkchains();
if("msg_end!" == token)
            }
{
// when all devices have indicated end of communication,
// create the chains
//
if((!!(status & 2)))
{
--iDevicesAround;
iDevicesAround = (((llAbs((iDevicesAround) >= (0))) * (iDevicesAround)) + ((
                      llAbs((iDevicesAround) < (0))) * (0)));


            return;
if(!(iDevicesAround))
        }
{
mkchains();
}
}


        if("identity" == token)
return;
        {
}
            ++iDevicesAround;
            llRegionSayTo(other_device, -20131224, llDumpList2String([llGetLinkKey(llGetLinkNumber()), other_device, "RFEDIP-1.0", "tether"], "|"));
            return;
        }


        llRegionSayTo(other_device, -20131224, llDumpList2String([llGetLinkKey(llGetLinkNumber()), other_device, "RFEDIP-1.0", "msg_end!", -20131224], "|"));
if("identity" == token)
    }
{
    touch_start(integer t)
// when other devices identify themselves, query them for tether points
    {
//
        if(!(llGetListLength(lChains)))
++iDevicesAround;
        {
llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice,
            llSay(0, "(" + (string)( (61440 - llGetUsedMemory() ) >> 10) + "kB) ~> " + "no chains to create");
              other_device, "RFEDIP-1.0", "tether"], "|"));
            return;
return;
        }
}


        if(( !!(status & 1) ) )
if((!!(status & 4)) && (llList2String(payload, 3) == "tether"))
        {
{
            llSay(0, "(" + (string)( (61440 - llGetUsedMemory() ) >> 10) + "kB) ~> " + "busy, please wait");
// answer queries from other devices when this device is chained
            return;
//
        }
integer _n = llGetListLength(lChains);


        (status += 1 * !(status & 1) );
while(_n)
        integer _n = llGetListLength(lChains);
{
_n -= 2;
llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice,
              other_device, "RFEDIP-1.0", "tether point", llGetLinkKey(llList2Integer(lChains,
                      (_n)))], "|"));
}
}


        while(_n)
llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice,
        {
              other_device, "RFEDIP-1.0", "msg_end!", -20131224], "|"));
            _n -= 3;
}
            llLinkParticleSystem(llList2Integer(lChains, (_n) + 1), []);
            (lChains = llListReplaceList(lChains, (list)NULL_KEY, (_n) + 2, (_n) + 2));
        }


        llSay(0, "(" + (string)( (61440 - llGetUsedMemory() ) >> 10) + "kB) ~> " + "querying for devices");
touch_start(integer t)
        llPlaySound("971bc958-ea04-194f-a78a-12826264dae4", 1);
{
        lTargets = [];
// [...]
        iDevicesAround = 0;
iDevicesAround = 0;
        llSetTimerEvent(30.0);
// [...]
        llRegionSay(-20131224, "identify");
        (status += 2 * !(status & 2) );
    }
    timer()
    {
        llSay(0, "(" + (string)( (61440 - llGetUsedMemory() ) >> 10) + "kB) ~> " + "timeout");
        mkchains();
    }
    state_entry()
    {
        status = 0;
        (lChains = getlinknumbersbylistnamedappend_attached(["Chain-0", "Chain-1", "Chain-2", "Chain-3", "Chain-4", "Chain-5", "Chain-6", "Chain-7", "Chain-8", "Chain-9", "Chain-10", "Chain-11"]));
        integer _n = llGetListLength(lChains);


        while(_n)
// ask other devices to identify
        {
//
            _n -= 3;
llRegionSay(-20131224, "identify");
            llSetLinkPrimitiveParamsFast(llList2Integer(lChains, (_n) + 1), [PRIM_TEXT, "", <0, 0, 0>, 0.0]);
}
        }


        llListen(-20131224, "", NULL_KEY, "");
// [...]
    }
    changed(integer w)
    {
        if(w & CHANGED_LINK)
        {
            integer _n = llGetListLength(lChains);
 
            while(_n)
            {
                _n -= 3;
                llLinkParticleSystem(llList2Integer(lChains, (_n) + 1), []);
                (lChains = llListReplaceList(lChains, (list)NULL_KEY, (_n) + 2, (_n) + 2));
            }
 
            (lChains = getlinknumbersbylistnamedappend_attached(["Chain-0", "Chain-1", "Chain-2", "Chain-3", "Chain-4", "Chain-5", "Chain-6", "Chain-7", "Chain-8", "Chain-9", "Chain-10", "Chain-11"]));
        }
    }
}
}
</lsl>
</lsl>


===Reference Implementation===


Since both the examples above leave to wish --- the first one because it re-creates the list of target points for every query, the second one because the algorithm that figures out which chain goes where still doesn´t do it right despite it was improved --- this section provides a reference implementation of both examples.
You can find the source for this device in the [https://github.com/Ratany/lsl-repo git repository] and further documentation in this [[How_to_make_writing_LSL_scripts_easier|article]].


====Tethering Device====
The reference implementation is available with some example objects for free on the [https://marketplace.secondlife.com/p/rfedip-reference-implementation/5644685 marketplace].


The tethering device has been modified so it doesn´t re-create the list of target points for every query:
The code on this wiki page is more recent than the code with the example devices because some optimizations have been applied.
 
<cpp>
// Ratanay fine Engineering Device Interface Protocol
//
// RfE-dip compatible generic tether point device
//
// "Generic" means that all prims of the object (i. e. device) this
// script is in which are named sCHAINPOINT (i. e. "hook", see below)
// will be reported via the Ratany fine Engineering device interface
// protocol when a compatible device queries this device.  In case
// there are no prims with that name and the script is in the root
// prim, the root prim will be reported.
 
 
// rfedip documentation:
//
// This device can be queried for the UUIDs of prims to which chains
// can be attached.  The token to query the device is "tether".  The
// device replies with the token "tether point", followed by the UUID
// of a chaining point.  Each chaining point is reported in a seperate
// response message, one chaining point per message.  When all
// chaining points have been reported, this devices sends a
// RFEDIP_protEND control message.
//
 
 
// some standard definitions
//
#include <lslstddef.h>
 
// use the getlinknumbersbyname() function from a library that
// provides functions to deal with names and descriptions of prims in
// order to figure out link numbers
//
#define _USE_getlinknumbersbyname
#include <getlinknumbers.lsl>
 
// some standard definitions for rfedip:
//
#include <rfedip.h>
 
// some definitions needed by devices that communicate with this
// device
//
#include <rfedip-generic-chainpoints.h>
 
 
// the prims of the device that are points to attach chains to are
// named "hook", unless otherwise defined
#ifndef sCHAINPOINT
#define sCHAINPOINT                "hook"
#endif
 
 
#define kHookKey(_n)              llGetLinkKey(llList2Integer(HOOKSLIST, _n))
 
key kThisDevice;                  // the UUID of this device
 
list lHooks;
#define HOOKSLIST                  lHooks
 
#define xHooksInit                HOOKSLIST = getlinknumbersbyname(sCHAINPOINT); unless(Len(HOOKSLIST)) { HOOKSLIST = (list)llGetLinkNumber(); }
 
 
default
{
    event state_entry()
    {
        // Remember the link numbers of the chain targets
        // rather than creating the list every time the device
        // is queried.
        //
        xHooksInit;
 
        kThisDevice = llGetLinkKey(llGetLinkNumber());
 
        // permanently listen on the protocol channel
        //
        llListen(RFEDIP_CHANNEL, "", NULL_KEY, "");
    }
    event changed(int w)
    {
        // maybe do something else here if you need to sit on
        // this device
        //
        when(w & CHANGED_LINK)
            {
                kThisDevice = llGetLinkKey(llGetLinkNumber());
                xHooksInit;
            }
    }
 
    event listen(int channel, string name, key other_device, string _MESSAGE)
    {
        IfMessage(RFEDIP_protIDENTIFY_QUERY)
        {
            // Indistinctively answer queries that want to
            // detect this device: The answer goes to the
            // sender (i. e. other_device) and looks like:
            //
            // "<sender-uuid>|<recipient-uuid>|RFEDIP_sVERSION|RFEDIP_protIDENTIFY"
            //
            // For the device that receives the answer to
            // the request to identify, the <sender-uuid>
            // is the UUID of this device.
            //
            // With
            //
            //  RFEDIP_RESPOND(<recipient-uuid>, <sender-uuid>, <protocol-payload>);
            //
            // an answer is sent to the device from which
            // this device has received the request to
            // identify itself:
            //
 
 
            RFEDIP_RESPOND(other_device, kThisDevice, RFEDIP_CHANNEL, RFEDIP_protIDENTIFY);
            return;
        }
 
        // From here on, received messages are expected to
        // look like:
        //
        // "<sender-uuid>|<recipient-uuid>|RFEDIP_sVERSION|<token>[|parameter|parameter...]"
        //
        // <sender-uuid> is the UUID of the device sending the
        // message (i. e. other_device); <recipient-uuid> is
        // the UUID of the recipient, i. e. of this device
        //
        // Please do not confuse incoming messages with
        // outgoing messages!
        //
        // Parameters can be tokens.  However, this is not
        // recommended.  Send multiple messages rather than
        // multiple tokens in one message.
        //
        // The received message is put into a list for further
        // processing:
        //
        list payload = ProtocolData(RFEDIP_sSEP);
 
        // Attempt to verify whether the message looks valid:
        //
        if(Len(payload) < RFEDIP_iMINMSGLEN) return;
 
        // Attempt to make sure that the message is for this
        // device:
        //
        // + ignore messages that appear not to be sent by the
        //  device they claim to be sent from by verifying
        //  the sender given in the message with the actual
        //  sender:
        //
#define InvalidSender (RFEDIP_ToSENDER(payload) != other_device)
        //
        // + ignore messages that appear not be destined for
        //  this device by verifying the recipient given in
        //  the message with what this device actually is:
        //
#define NotDestined  (RFEDIP_ToRCPT(payload) != kThisDevice)
        //
        // + ignore messages that request a different version
        //  of the protocol by verifying the protocol version
        //  given in the message --- in this example, a check
        //  for what is considered a "sufficient version" is
        //  applied:
        //
#define BadVersion    !Instr(RFEDIP_ToPROTVERSION(payload), RFEDIP_sSUFFICIENT_VERSION)
        //
        when(InvalidSender || NotDestined || BadVersion) return;
 
#undef InvalidSender
#undef NotDestined
#undef BadVersion
 
        // From here on, the capabilities of the device can be
        // implemented.
        //
        // Here: Report the chain target points and send of of
        // communication message.
        //
        when(RFEDIP_ToFirstTOKEN(payload) == protTETHER)
            {
                int n = Len(HOOKSLIST);
                LoopDown(n, RFEDIP_RESPOND(other_device, kThisDevice, RFEDIP_CHANNEL, protTETHER_RESPONSE, kHookKey(n)));
            }
 
        RFEDIP_END(other_device, kThisDevice, RFEDIP_CHANNEL);
    }
 
    event on_rez(int p) { kThisDevice = llGetLinkKey(llGetLinkNumber()); }
}
</cpp>
 
Compiled:
 
<lsl>
list getlinknumbersbyname(string name)
{
    integer n = llGetObjectPrimCount(llGetLinkKey(llGetLinkNumber() ) );
    list numbers = [];
    name = llToLower(name);
 
    if(n > 1)
    {
        while(n)
        {
            if((~llSubStringIndex(llToLower(llList2String(llGetLinkPrimitiveParams(n, [PRIM_NAME] ), 0) ), name) ) )
            {
                numbers += n;
            }
 
            --n;
        }
    }
    else
    {
        if((~llSubStringIndex(llToLower(llGetObjectName() ), name) ) )
        {
            numbers += n;
        }
    }
 
    return numbers;
}
key kThisDevice;
list lHooks;
 
default
{
    state_entry()
    {
        lHooks = getlinknumbersbyname("hook");
 
        if(!(llGetListLength(lHooks)))
        {
            lHooks = (list)llGetLinkNumber();
        }
 
        kThisDevice = llGetLinkKey(llGetLinkNumber());
        llListen(-20131224, "", NULL_KEY, "");
    }
    changed(integer w)
    {
        if(w & CHANGED_LINK)
        {
            kThisDevice = llGetLinkKey(llGetLinkNumber());
            lHooks = getlinknumbersbyname("hook");
 
            if(!(llGetListLength(lHooks)))
            {
                lHooks = (list)llGetLinkNumber();
            }
        }
    }
    listen(integer channel, string name, key other_device, string _MESSAGE)
    {
        if("identify" == _MESSAGE)
        {
            llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice, other_device, "RFEDIP-1.0", "identity"], "|"));
            return;
        }
 
        list payload = llParseString2List((_MESSAGE), ["|"], []);
 
        if(llGetListLength(payload) < 4) return;
 
        if((llList2Key(payload, 0) != other_device) || (llList2Key(payload, 1) != kThisDevice) || !(~llSubStringIndex(llList2String(payload, 2), "RFEDIP") )) return;
 
        if(llList2String(payload, 3) == "tether")
        {
            integer n = llGetListLength(lHooks);
 
            while(n)
            {
                --n;
                llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice, other_device, "RFEDIP-1.0", "tether point", llGetLinkKey(llList2Integer(lHooks, n))], "|"));
            }
        }
 
        llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice, other_device, "RFEDIP-1.0", "msg_end!", -20131224], "|"));
    }
    on_rez(integer p)
    {
        kThisDevice = llGetLinkKey(llGetLinkNumber());
    }
}
</lsl>
 
====Chaining Device====
 
The algorithm has been improved, and the device can now report its chain starting points as chain target points like the tethering device.  This feature has been added for users who might have several boats or airships which they might park close to each other.  It is not uncommon to tie a number of boats to each other to secure them.
 
<cpp>
 
// Ratany fine Engineering Interface Device Protocol device
//
// implementation of a device that queries another rfedip device for
// chaining points and then makes use of the points --- this device
// also makes its chain starting points available when the device
// itself is chained to something
//
// see https://wiki.secondlife.com/wiki/LSL_Protocol/rfedip
//
// Use this device to tie up your boats or airships or something :)
 
 
// rfedip documentation:
//
// This device can be queried for the UUIDs of prims to which chains
// can be attached.  The token to query the device is "tether".  The
// device replies with the token "tether point", followed by the UUID
// of a chaining point.  Each chaining point is reported in a seperate
// response message, one chaining point per message.  When all
// chaining points have been reported, this devices sends a
// RFEDIP_protEND control message.
//
// When this device is has not created chains itself, it does not
// report its chain target points but sends a RFEDIP_protEND control
// message instead.
//
// This device can send queries for the UUIDs of prims to which chains
// can be attached.  The token used for the query is "tether".  This
// device expects answers to this query to use the token "tether
// point", followed by the UUID of the point as parameter.  It expects
// devices that have received a query to indicate that all their
// UUUIDs have been reported by sending a a RFEDIP_protEND control
// message, regardless whether any points have been reported or not.
//
 
 
// You can select what to use as reference point for linking chains by
// segments: Depending on the particular object and on how the chain
// starting points are arranged, it can be better to use the geometric
// center of the object than it is to use the average positions of the
// chain starting points.  Alternatively, you can use the center of
// the bounding box of the object as reference.
//
// Code is provided for any of those, and which will be used depends
// on 'GeometricCenterIsReference' and 'BoundingBoxCenterIsReference'
// below.  The two are mutually exclusive.  It may also make sense to
// use a totally different point than any of those.
//
//
// Note: The algorithm does NOT consider in which segments relative to
// the object a chain starting point resides.  Instead, it figures out
// in which segments relative to the reference point a chain starting
// point resides.  Which segment that is changes with the rotation of
// the object.  The same is true for the segments of the chain target
// points.
//
// When an object is rotated in such a way that a chain starting point
// which was at the left back of the reference point is now at the
// front right of the reference point, its chain becomes likely to be
// linked to a target point which is also at the right front of the
// reference point.  Insofar the target points do not rotate with the
// object, the same chain would be linked to a different target point.
//
// When the object is rotated in between, it may look odd for human
// observers that a chain goes from what they perceive as the left
// back of the object to a target point which they perceive to be at
// the right back of the object.  This happens when the chain starting
// point, due to rotation, is actually at the left right of the
// reference point while the observers still see it as on the left
// back of the object and would put the chain to a target point which
// is also at the left back of the object.  The more the reference
// point is off of the middle point of the object, the stronger this
// effect may be, and the target point which is perceived as at the
// left back of the object may even be closer to the chaining point
// than the target point the algorithm links the chain to.
//
// Ideally, one would use the middle point of the object as reference
// point.  Unfortunately, what a human observer of the object would
// consider as middle point would be anything but easy to compute.
// Besides the points used here, for physical objects one could also
// experiment with llGetCenterOfMass().
//
// That the middle point of the object remains unknown and the
// uncomputable factor of human perception makes it impossible to get
// perfect results for all situations with a generic algorithm like
// this.  Good results for most situations will have to be sufficient
// here.
//
// Generally, the more evenly the chain starting points are
// distributed about the object --- for example, in an arrangement
// that resembles a circle along the circumference of the object ---
// the better it is to use the average position of the chain starting
// points as reference, especially when this average position diverges
// greatly from the geometric center of the object (or another
// reference point) in such a way that the geometric center (or the
// other reference point) is further off from what a human observer
// would percieve as middle point than the average position of the
// chain starting points is.
//
// You can adjust the reference point for a specific object you want
// to use this algorithm with by specifying it as an offset to the
// root prim.  You need to change the #defines of
// 'ObjectspecificIsReference' and 'vOBJECT_REFPOINT' for this.
//
// When you tie up a vehicle or other object which is moving while the
// algorithm is computing where to put which chain, you may get
// undesireable results because the movement of the object can
// invalidate the reference point.  If you cannot prevent the object
// from moving about while the computation is ongoing, you might get
// better results by using an external point as reference point, like
// the average positions of the chain target points.  Limiting which
// chain target points are considered, for example by distance and/or
// ownership, and even a suitable arrangement of the chain target
// points, may be required under such adverse conditions.
//
// To tie up an airship or boat that doesn´t stop moving, it may be
// advisable to use a sufficiently small (virtual) berth inside of
// which the entire airship needs to be before it can be tied down.
// Such a berth itself would use the rfedip protocol to interoperate
// with vehicles.  A particularly good reference point would be the
// middle point of the berth, which could be as simple to find out as
// the position of a box the vehicle tightly fits inside of is.
//
//
// When 'GeometricCenterIsReference' is defined as something that
// evaluates to TRUE, the geometric center of the object is used as
// reference.  When 'BoundingBoxCenterIsReference' is defined as
// something that evaluates to TRUE, the center of the objects´
// bounding box is used as reference.  When
// 'ObjectspecificIsReference' is defined as something that evaluates
// to TRUE, an offset to the root prim given in 'vOBJECT_REFPOINT' is
// used as reference.  When all these are defined as something that
// evaluates to TRUE, an error message will be printed.
//
// When all these are defined as something that evaluates to FALSE,
// the average position of the chain starting points is used as
// reference point.
//
// (Do not use TRUE or FALSE here unless you have defined them
// somewhere.  Use 1 and 0.)
//
#define GeometricCenterIsReference          0
#define BoundingBoxCenterIsReference        0
#define ObjectspecificIsReference            0
#if ObjectspecificIsReference
#define vOBJECT_REFPOINT                    (ZERO_VECTOR)
#endif
//
 
 
// This particular device can have multiple chains, created from the
// prims with names listed here.  Add as many chains as you like,
// until the script runs out of memory.
//
#define lCHAINTOS                  ["Chain-0", "Chain-1", "Chain-2", "Chain-3", "Chain-4", "Chain-5", "Chain-6", "Chain-7", "Chain-8", "Chain-9", "Chain-10", "Chain-11"]
//#define lCHAINTOS                  ["Chain-0", "Chain-1", "Chain-2", "Chain-3"]
 
 
// You can have tons of debugging output with these:
//
#define DEBUG0 0
#define DEBUG1 0
#define DEBUG2 0
#define fDEBUGSLEEP                10.0
//
 
 
//
// UNLESS YOU WANT TO CHANGE THE CHANGE THE SCRIPT, THERE ISN´T
// ANYTHING TO CHANGE BELOW THIS LINE
//
 
 
#define DEBUG DEBUG0 || DEBUG1 || DEBUG2
 
 
// some standard definitions
//
#include <lslstddef.h>
#include <colordef.h>
 
// use the getlinknumbersbylistnamedappend_attached() function from a
// library that provides functions to deal with names and descriptions
// of prims in order to figure out link numbers
//
#define _USE_getlinknumbersbylistnamedappend_attached_notnamed
#define _GETLINKNUMBERSBYLISTNAMEDAPPEND_ATTACHED_NOTNAMED_APPENDIX NULL_KEY
#include <getlinknumbers.lsl>
 
// use the geom_linecrosspoint() function from a library that provides
// functions that deal with geometry
//
#define _GEOM_USE_geom_linesegmentscross
#define _GEOM_USE_segments_undef_flags
#define _GEOM_USE_segments
#define _GEOM_USE_geom_nofsharedsegments3D
 
#if DEBUG2
#define _GEOM_USE_geom_segments2list3D
#endif
 
#include <geometry.lsl>
//
// this is defined to easily switch the very function used
//
#define boolChainsAreCrossing(_v1, _v2, _v3, _v4) geom_linesegmentscross(_v1, _v2, _v3, _v4)
 
// some standard definitions for rfedip:
//
#include <rfedip.h>
 
// some definitions from devices that communicate with this
// device
//
#include <rfedip-generic-chainpoints.h>
 
 
// The standard library (lslstddef.h) provides status handling, which
// requires an integer to store the status´ in:
//
int status;
#define stBUSY                    1
#define stRECEIVING                2
#define stHASCHAINS                4
 
 
key kThisDevice;                        // the UUID of this device
int iDevicesAround;                    // keep track of how many other devices have been found
#define iSTRIDE_lChains            2    // lChains is a strided list: [link number, uuid of target]
#define iINDEXOFFSET_lChains      1    // needed to deduct which chain is linked to a given target point
list lChains;                          // lChains is a strided list: [link number, uuid of target], see notes below
list lTargets;                          // a list of uuids of prims to leash to, as reported via rfedip
 
// a few macro definitions to deal with the chains
//
// Consider them as a demonstration of the incredible usefulness of a
// preprocessor.
//
 
#define boolChainLinked(_n)        (kChainKey(_n) != NULL_KEY)
#define iChainLinkNo(_n)          llList2Integer(lChains, (_n))
#define kChainKey(_n)              llList2Key(lChains, (_n) + 1)
#define sChainName(_n)            llList2String(GLPP(iChainLinkNo(_n), [PRIM_NAME]), 0)
#define vChainEndpos(_c)          RemotePos(kChainKey(_c))
#define vChainPos(_chain)          (llList2Vector(GLPP(iChainLinkNo(_chain), [PRIM_POSITION]), 0))
#define xChainHide(_n)            llLinkParticleSystem(iChainLinkNo(_n), [])
#define xChainShow(_n, _vcolour)  llLinkParticleSystem(iChainLinkNo(_n), CHAINS_lPARTICLE_CHAIN(_n, _vcolour))
#define yChainLink(_n, _k)        (lChains = llListReplaceList(lChains, (list)_k, (_n) + 1, (_n) + 1))
#define yChainUnlink(_n)          (lChains = llListReplaceList(lChains, (list)NULL_KEY, (_n) + 1, (_n) + 1))
#define yChainsInit                (lChains = getlinknumbersbylistnamedappend_attached_notnamed(lCHAINTOS))
 
#define LoopChains(_do)            int _n = Len(lChains); while(_n) { _n -= iSTRIDE_lChains; _do; }
 
#define kTargetKey(_target)        llList2Key(lTargets, _target)
#define sTargetName(_target)      RemoteName(kTargetKey(_target))
#define vTargetPos(_target)        RemotePos(kTargetKey(_target))
 
#define CHAINS_lPARTICLE_CHAIN(_n, _vc)  [                  \
                      PSYS_PART_MAX_AGE, 3.0,      \
                                      PSYS_PART_FLAGS, PSYS_PART_FOLLOW_VELOCITY_MASK | PSYS_PART_TARGET_POS_MASK | PSYS_PART_FOLLOW_SRC_MASK | PSYS_PART_EMISSIVE_MASK, \
                                      PSYS_PART_START_COLOR, _vc, \
                                      PSYS_PART_START_SCALE, <0.2, 0.2, 0.0>, \
                                      PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_DROP, \
                                      PSYS_SRC_BURST_RATE, 0.04,    \
                                      PSYS_SRC_ACCEL, <0.0, 0.0, -1.2>, \
                                      PSYS_SRC_BURST_PART_COUNT, 1, \
                                      PSYS_SRC_BURST_SPEED_MIN, 3.0,    \
                                      PSYS_SRC_BURST_SPEED_MAX, 3.0,    \
                                      PSYS_SRC_TARGET_KEY, kChainKey(_n), \
                                      PSYS_SRC_MAX_AGE, 0.0,        \
                                      PSYS_SRC_TEXTURE, "4cde01ac-4279-2742-71e1-47ff81cc3529" \
                    ]
 
// unfortunately, this device needs a timer because messages can be
// lost, or no compliant devices might be around
//
#define fTIMER_UNWAIT              30.0
 
// sometimes stuff is undetermined
//
#define fUNDETERMINED              -1.0
#define iUNDETERMINED              -1
#define vUNDETERMINED              (<fUNDETERMINED, fUNDETERMINED, fUNDETERMINED>)
#define fIsUndetermined(_f)        (fUNDETERMINED == _f)
#define iIsUndetermined(_i)        (!~_i)
#define vIsUndetermined(_v)        (vUNDETERMINED == _v)
 
 
 
////////////////////////////////////////////////////////////////////////////////
 
 
// boolSegmentEndsAbove() is to handle a special case:  The function
// to determine whether two line segments cross or not considers two
// dimensions only (x and y coordinates).  This makes sense because it
// is somewhat unlikely for two chains to cross each other exactly
// when three dimensions (x, y and z coordinates) are considered.
//
// The special case is that the end points or starting points of two
// chains may have the same x and y coordinates while they have
// different z coordinates.  This happens when two target points or
// starting points are above each other.  The chains would be
// considered as crossing each other (at their end/start points),
// which is not desirable for this application.
//
// boolSegmentEndsAbove() is used to detect this special case,
// with some tolerance, and to make the chains considered as not
// crossing each other.  Doing this with some tolerance seems
// advisable not so much because of rounding errors but more so
// because prims may not be positioned too precisely.
//
bool boolSegmentEndsAbove(vector A, vector B, vector C, vector D)
{
    // let the preprocessor figure it out ...
    //
#define fTOLERANCEBY              100.0
#define tolerant(_f)              ((float)llRound(_f * fTOLERANCEBY) / fTOLERANCEBY)
#define cmpxy(_x, _y)              (tolerant(_x) == tolerant(_y))
#define cmpabz(_va, _vb)          (cmpxy(_va.x, _vb.x) && cmpxy(_va.y, _vb.y) && (_va.z != _vb.z))
#define cmp(_va, _vb, _vc, _vd)    (cmpabz(_va, _vb) || cmpabz(_vc, _vd))
 
    // ... and simply return the result of the comparison
    //
    return cmp(A, B, C, D);
 
#undef cmp
#undef cmpabz
#undef cmpxy
#undef tolerant
#undef fTOLERANCEBY
 
// This is only implemented as a function because it seems to eat less
// memory with this script that way.
//
}
 
 
// figure out which target shares the most segments with a chain and
// is closest to the chain starting point
//
int closest_shared(vector ref, int chain)
{
    vector chainpos = vChainPos(chain);
    vector chainrefpos = PosOffset(ref, chainpos);
 
    int shared = 0;
    int whicht = iUNDETERMINED;
    vector post;
 
    int t = Len(lTargets);
    while(t)
        {
            --t;
 
            vector thispos = vTargetPos(t);
            int segments = geom_nofsharedsegments3D(PosOffset(ref, thispos), chainrefpos);
            if(shared < segments)
                {
                    shared = segments;
                    whicht = t;
                    post = thispos;
                }
 
        }
 
    when(iIsUndetermined(whicht)) return whicht;
 
 
    float maxdist = llVecDist(post, chainpos);
 
    int ret = iUNDETERMINED;
 
    t = Len(lTargets);
    while(t)
        {
            --t;
 
            vector thispos = vTargetPos(t);
            float thisdist = llVecDist(thispos, chainpos);
            when(thisdist < maxdist)
                {
                    when(geom_nofsharedsegments3D(PosOffset(ref, thispos), chainrefpos) >= shared)
                        {
                            maxdist = thisdist;
                            ret = t;
                        }
                }
        }
 
    unless(iIsUndetermined(ret)) return ret;
 
    return whicht;
}
 
 
// figure out which chain target is closest to a given chain point
//
int closest_target(int chain)
{
    int ret = iUNDETERMINED;
    vector chainpos = vChainPos(chain);
    float closest = fUNDETERMINED;
    int n = Len(lTargets);
    while(n)
        {
            --n;
 
            float distance = llVecDist(vTargetPos(n), chainpos);
            if(fIsUndetermined(closest))
                {
                    closest = distance;
                    ret = n;
                }
            else
                {
                    if(distance < closest)
                        {
                            closest = distance;
                            ret = n;
                        }
                }
        }
 
    DEBUGmsg0("closest target:", closest);
    return ret;
}
 
 
// swap the target points of two chains
//
void swap_chains(int ca, int cb)
{
    key tmp = kChainKey(ca);
 
    yChainLink(ca, kChainKey(cb));
    yChainLink(cb, tmp);
}
 
 
// figure out how many chains cross a given chain and pick one that
// crosses it
//
list find_chain_crossedby(int chain)
{
    vector start = vChainPos(chain);
    vector end = vChainEndpos(chain);
 
    int many = 0;
    int which = iUNDETERMINED;
 
    int c = Len(lChains);
    while(c)
        {
            c -= iSTRIDE_lChains;
 
            when((chain != c) && boolChainLinked(c))
                {
                    vector cpos = vChainPos(c);
                    vector cend = vChainEndpos(c);
                    unless(boolSegmentEndsAbove(end, cend, start, cpos))
                        {
                            if(boolChainsAreCrossing(start, end, cpos, cend))
                                {
                                    DEBUGmsg0(sChainName(c), "crosses", sChainName(chain));
                                    ++many;
                                    which = c;
                                }
                        }
                }
        }
 
    return [which, many];
}
 
 
#if DEBUG
void show()
{
    DEBUGmsg("show intermediate results");
    LoopChains(when(boolChainLinked(_n)) { vector color = RandomColor; xChainShow(_n, color); SLPPF(iChainLinkNo(_n), [PRIM_TEXT, sChainName(_n), color, 1.0]); });
    llSleep(fDEBUGSLEEP);
}
#endif
 
 
void hide()
{
    aftell("removing chains");
    LoopChains(xChainHide(_n); yChainUnlink(_n));
    UnStatus(stHASCHAINS);
}
 
 
// tie up an object so that the result doesn´t look retarded
//
// caveats: It is undetermined whether a solution can be found in all
//          possible cases or not.
//
//          The algorithm scales horribly.
//
void mkchains()
{
    UnStatus(stRECEIVING);
    llSetTimerEvent(0.0);
 
    unless(Len(lChains) && Len(lTargets))
        {
            aftell("no chains or targets to work with");
            UnStatus(stBUSY);
            return;
        }
 
    aftell("computing ...");
 
 
    DEBUGmsg1("computing segment reference point");
 
#if BoundingBoxCenterIsReference && GeometricCenterIsReference && ObjectspecificIsReference
    ERRORmsg("'BoundingBoxCenterIsReference', 'GeometricCenterIsReference' and 'ObjectspecificIsReference' are mutually exclusive");
#else
#if ObjectspecificIsReference
    DEBUGmsg1("reference: object specific offset to root prim");
    vector refc = vOBJECT_REFPOINT * llGetRootRotation() + llGetRootPosition();
    int c;
#else
#if BoundingBoxCenterIsReference
    // use the center of the bbx of the object as reference
    //
    DEBUGmsg1("reference: bbx center");
    vector refc = BbxCenterPos(llGetKey());
    int c;
#else
#if GeometricCenterIsReference
    // use the geometric center of the object as reference point
    //
    DEBUGmsg1("reference: geometric center");
 
    vector refc = llGetGeometricCenter() * llGetRootRotation() + llGetRootPosition();
    int c;
#else
    // the average position of the chain points is the reference
    // point for the segments
    //
    DEBUGmsg1("reference: geometric center");
 
    vector refc = ZERO_VECTOR;
    int count = 0;
    int c = Len(lChains);
    while(c)
        {
            c -= iSTRIDE_lChains;
            refc += vChainPos(c);
            ++count;
        }
 
    DEBUGmsg2(count, "chain points");
    refc /= (float)(count + !count);
#endif  // GeometricCenterIsReference
#endif  // BoundingBoxCenterIsReference
#endif  // ObjectspecificIsReference
 
 
    // link each chain to closest target that shares the most
    // segments with the chain point
    //
    DEBUGmsg1("linking by segments");
 
#if DEBUG
    DEBUGmsg("segment reference point:", refc);
    if(HasInventory("sph-stick"))
        {
            // to visualize the reference point, rezz a "stick" which deletes
            // itself after 120 seconds
            //
            llRezAtRoot("sph-stick", refc, ZERO_VECTOR, ZERO_ROTATION, 120);
        }
#endif
 
    c = Len(lChains);
    while(c)
        {
            c -= iSTRIDE_lChains;
 
            int t = closest_shared(refc, c);
            unless(iIsUndetermined(t))
                {
                    DEBUGmsg2("chain", sChainName(c), "at", PosOffset(refc, vChainPos(c)), "is in segments", llList2CSV(geom_segments2list3D(PosOffset(refc, vChainPos(c)))));
                    DEBUGmsg2("link", sChainName(c), "to", sTargetName(t), "(", t, ")", "at", PosOffset(refc, vTargetPos(t)), "which is in segments", llList2CSV(geom_segments2list3D(PosOffset(refc, vTargetPos(t)))));
 
                    yChainLink(c, kTargetKey(t));
                    lTargets = llDeleteSubList(lTargets, t, t);
                }
#if DEBUG2
            else
                {
                    DEBUGmsg2("no target in segments of", sChainName(c));
                }
#endif
        }
 
#if DEBUG
    show();
#endif
 
    // restore targets
    //
    { LoopChains(when(boolChainLinked(_n)) Enlist(lTargets, kChainKey(_n))); }
 
    // disentangle the chains
    //
    DEBUGmsg1("disentangling chains");
 
    int tries = Len(lChains);
    bool swapped;
    do
        {
            swapped = FALSE;
 
            c = Len(lChains);
            while(c)
                {
                    c -= iSTRIDE_lChains;
                    unless(boolChainLinked(c)) continue next_chain;
 
                    // disentangle the chains: Swap this chain with another chain that
                    // crosses this chain, unless swapping the chains entangles all of
                    // them more.
                    //
 
                    // make the list returned by find_chain_crossedby() useable:
                    //
#define xWhich(_l) llList2Integer(_l, 0)
#define xMany(_l)  llList2Integer(_l, 1)
                    //
 
                    list crosses = find_chain_crossedby(c);
                    unless(iIsUndetermined(xWhich(crosses)))
                        {
                            DEBUGmsg0("swap:", sChainName(c), "and", sChainName(xWhich(crosses)), "/", xMany(crosses), "xings");
 
                            swap_chains(c, xWhich(crosses));
                            swapped = TRUE;
 
                            list more = find_chain_crossedby(c);
                            if(!iIsUndetermined(xWhich(more)) && (xMany(crosses) < xMany(more)))
                                {
                                    DEBUGmsg0("unswap:", sChainName(c), "and", sChainName(xWhich(crosses)), "/", xMany(crosses), "xings vs.", xMany(more));
 
                                    swap_chains(c, xWhich(crosses));
                                    swapped = FALSE;
                                }
#if DEBUG0
                            else
                                {
                                    DEBUGmsg0("no unswap:", xMany(more) - xMany(crosses), "xings");
                                }
#endif
                        }
 
#undef xMany
#undef xWhich
 
                    // disentangle the chains even more: See if there is a target-point
                    // closer to the chain-point of this chain which has an alternative
                    // chain linked to it, and swap this chain and the alternative chain
                    // when this chain and the alternative chain are crossing each other.
                    //
                    // (Swapping chains that do not cross each other can make them cross
                    // each other, and they might be swapped back (by above), effectively
                    // leading to swapping them back and forth indefinitely.)
                    //
                    // When there is a closer target with no chain linked to it, move this
                    // chain over to the new target, unless the new target shares less
                    // segments with the chain starting position than the current one.
                    //
                    int alternative_target = closest_target(c);
                    unless(iIsUndetermined(alternative_target))
                        {
                            vector chainpos_c = vChainPos(c);
                            vector endpos_c = vChainEndpos(c);
                            vector endpos_alt = vTargetPos(alternative_target);
 
                            if(llVecDist(chainpos_c, endpos_alt) < llVecDist(chainpos_c, endpos_c))
                                {
                                    int alternative_chain = LstIdx(lChains, kTargetKey(alternative_target));
                                    unless(iIsUndetermined(alternative_chain))
                                        {
                                            alternative_chain -= iINDEXOFFSET_lChains;
 
                                            vector chainpos_alt = vChainPos(alternative_chain);
 
                                            unless(boolSegmentEndsAbove(endpos_c, endpos_alt, chainpos_c, chainpos_alt))
                                                {
                                                    when(boolChainsAreCrossing(chainpos_c, endpos_c, chainpos_alt, endpos_alt))
                                                        {
                                                            DEBUGmsg0("swapping closer:", sChainName(c), "with", sChainName(alternative_chain));
                                                            swap_chains(c, alternative_chain);
                                                            swapped = TRUE;
                                                        }
                                                }
                                        }
                                    else
                                        {
                                            DEBUGmsg0("no chain to swap with");
 
                                            vector rel_chainpos = PosOffset(refc, chainpos_c);
                                            when(geom_nofsharedsegments3D(rel_chainpos, PosOffset(refc, endpos_alt)) >= geom_nofsharedsegments3D(rel_chainpos, PosOffset(refc, endpos_c)))
                                                {
                                                    DEBUGmsg0("moving", sChainName(c), "to", endpos_alt);
                                                    yChainLink(c, kTargetKey(alternative_target));
                                                    swapped = TRUE;
                                                }
#if DEBUG0
                                            else
                                                {
                                                    DEBUGmsg0(sChainName(c), "--- new targets are not supposed to suddenly appear here");
                                                }
#endif
                                        }
                                }
                        }
 
                    @next_chain;
                }
            --tries;
        }
    while(swapped && tries);


    // when the chains are linked and (hopefully) disentangled,
    // show the result:


    if(tries) aftell("solution found");
==References==


#if DEBUG
[https://marketplace.secondlife.com/p/rfedip-reference-implementation/5644685 Reference Implementation]
    show();
* [[How_to_make_writing_LSL_scripts_easier|How to make creating LSL scripts easier]] describes how to preprocess your sources with cpp.
#else
* [https://github.com/Ratany/lsl-repo Git repository] for the [[How_to_make_writing_LSL_scripts_easier|article]] that describes how to preprocess your sources with cpp and contains the reference implementations.
    LoopChains(when(boolChainLinked(_n)) { vector color = RandomColor; xChainShow(_n, color); });
#endif


    SetStatus(stHASCHAINS);
==History===
 
#endif  // BoundingBoxCenterIsReference && GeometricCenterIsReference && ObjectspecificIsReference
 
    UnStatus(stBUSY);
    aftell("done");
}
 
 
// This is a version of the template modified for this particular
// device.
//
default
{
    event listen(int channel, string name, key other_device, string _MESSAGE)
    {
        // identify this device
        //
        IfMessage(RFEDIP_protIDENTIFY_QUERY)
        {
            RFEDIP_RESPOND(other_device, kThisDevice, RFEDIP_CHANNEL, RFEDIP_protIDENTIFY);
            return;
        }
 
        // verify whether the received message looks like a
        // valid refdip-protocol message
        //
        list payload = ProtocolData(RFEDIP_sSEP);
        if(Len(payload) < RFEDIP_iMINMSGLEN) return;
        when((RFEDIP_ToSENDER(payload) != other_device) || (RFEDIP_ToRCPT(payload) != kThisDevice) || !Instr(RFEDIP_ToPROTVERSION(payload), RFEDIP_sSUFFICIENT_VERSION)) return;
 
        // extract the token from the rfedip message ...
        //
        string token = RFEDIP_ToFirstTOKEN(payload);
 
        //
        // ... to do some device specific stuff
        //
        // The order of the message handling has been arranged
        // for better performance.
        //
 
        when(protTETHER_RESPONSE == token)
            {
                // THIRD: Receive the responses to the query for points to attach chains to
                //        and remember those points.
                //
                Enlist(lTargets, RFEDIP_ToFirstPARAM(payload));
                return;
            }
 
        when(RFEDIP_protEND == token)
            {
                IfStatus(stRECEIVING)
                {
                    // Receiving an end-message means that the device which sent it has finished
                    // answering.
                    //
                    --iDevicesAround;
                    iDevicesAround = Max(iDevicesAround, 0);  // ignore unexpected end-msgs
 
                    // FOURTH: When all answers have been received, make the chains.
                    //        In case an end-message gets lost, the timeout kicks in.
                    //
                    unless(iDevicesAround) mkchains();
                }
 
                return;
            }
 
        when(RFEDIP_protIDENTIFY == token)
            {
                // SECOND: Ask a device that identifies itself for points to attach chains to.
                //
                ++iDevicesAround;
                RFEDIP_RESPOND(other_device, kThisDevice, RFEDIP_CHANNEL, protTETHER);
                return;
            }
 
        when(HasStatus(stHASCHAINS) && (RFEDIP_ToFirstTOKEN(payload) == protTETHER))
            {
                // allow others to chain up only when this device is tied
 
                LoopChains(RFEDIP_RESPOND(other_device, kThisDevice, RFEDIP_CHANNEL, protTETHER_RESPONSE, llGetLinkKey(iChainLinkNo(_n))));
                // RFEDIP_END(other_device, kThisDevice, RFEDIP_CHANNEL);
                // return;
            }
 
        // when receiving messages not handled by this device,
        // indicate end of communication to potentially save
        // other devices unnecessary waiting times
        //
        RFEDIP_END(other_device, kThisDevice, RFEDIP_CHANNEL);
    }
 
    event touch_start(int t)
    {
        unless(Len(lChains))
            {
                aftell("no chains to create");
                return;
            }
 
        IfStatus(stBUSY)
        {
            aftell("busy, please wait");
            return;
        }
 
        IfStatus(stHASCHAINS)
        {
            hide();
            return;
        }
 
        SetStatus(stBUSY);
 
        aftell("querying for devices");
        SoundPing;
        lTargets = [];
 
        // keep track of how many devices are detected to be
        // able to decide whether all answers from all devices
        // have been received or not
        //
        iDevicesAround = 0;
 
        // there isn´t a way around a timeout with this device :(
        //
        llSetTimerEvent(fTIMER_UNWAIT);
 
        // FIRST: query for rfedip-compliant devices
        //
        SetStatus(stRECEIVING);
        RFEDIP_IDQUERY;
    }
 
    event timer()
    {
        // A message indicating end of communication might not
        // have arrived.  Go ahead with whatever information
        // has been received so far.
        //
        apf("timeout,", iDevicesAround, "targets detected");
        mkchains();
    }
 
    event state_entry()
    {
        ClrStatus;
        yChainsInit;
 
#if DEBUG
        LoopChains(SLPPF(iChainLinkNo(_n), [PRIM_TEXT, sChainName(_n), GREEN, 1.0]));
#else
        LoopChains(SLPPF(iChainLinkNo(_n), [PRIM_TEXT, "", BLACK, 0.0]));
#endif
 
        kThisDevice = llGetLinkKey(llGetLinkNumber());
        llListen(RFEDIP_CHANNEL, "", NULL_KEY, "");
    }
 
    event changed(int w)
    {
        when(w & CHANGED_LINK)
            {
                // maybe do something else here if you need to sit on this device
                //
                hide();
                yChainsInit;
                kThisDevice = llGetLinkKey(llGetLinkNumber());
            }
    }
 
    event on_rez(int p)
    {
        kThisDevice = llGetLinkKey(llGetLinkNumber());
        hide();
    }
}
</cpp>
 
Compiled (with debugging disabled):
 
<lsl>
list getlinknumbersbylistnamedappend_attached_notnamed(list names)
{
    list strided = [];
    integer n = llGetNumberOfPrims();
 
    do
    {
        if(~llListFindList(names, llGetLinkPrimitiveParams(n, [PRIM_NAME]))) if((llGetUsedMemory() < 61440) && !~llListFindList(strided, [n, NULL_KEY]))
            {
                strided += [n, NULL_KEY];
            }
    }
    while(--n > 0);
 
    return strided;
}
integer geom_nofsharedsegments3D(vector A, vector B)
{
    return ((((A.x == 0.0) && (A.y == 0.0)) && ((B.x == 0.0) && (B.y == 0.0))) + ((A.x < 0.0) && (B.x < 0.0)) + ((A.x > 0.0) && (B.x > 0.0)) + ((A.y > 0.0) && (B.y > 0.0)) + ((A.y < 0.0) && (B.y < 0.0)) + ((A.z > 0.0) && (B.z > 0.0)) + ((A.z < 0.0) && (B.z < 0.0)));
}
integer geom_linesegmentscross(vector p1, vector p2, vector p3, vector p4)
{
    vector a = p2 - p1;
    vector b = p3 - p4;
    vector c = p1 - p3;
    float alphaNumerator = b.y * c.x - b.x * c.y;
    float alphaDenominator = a.y * b.x - a.x * b.y;
    float betaNumerator = a.x * c.y - a.y * c.x;
    float betaDenominator = a.y * b.x - a.x * b.y;
 
    if((alphaDenominator == 0.0) || (betaDenominator == 0.0)) return FALSE;
 
    if(alphaDenominator > 0.0)
    {
        if((alphaNumerator < 0.0) || (alphaNumerator > alphaDenominator)) return FALSE;
    }
    else if((alphaNumerator > 0.0) || (alphaNumerator < alphaDenominator)) return FALSE;
 
    if(betaDenominator > 0.0)
    {
        if((betaNumerator < 0.0) || (betaNumerator > betaDenominator)) return FALSE;
    }
    else if((betaNumerator > 0.0) || (betaNumerator < betaDenominator)) return FALSE;
 
    return TRUE;
}
integer status;
key kThisDevice;
integer iDevicesAround;
list lChains;
list lTargets;
integer boolSegmentEndsAbove(vector A, vector B, vector C, vector D)
{
    return (((((float)llRound(A.x * 100.0) / 100.0) == ((float)llRound(B.x * 100.0) / 100.0)) && (((float)llRound(A.y * 100.0) / 100.0) == ((float)llRound(B.y * 100.0) / 100.0)) && (A.z != B.z)) || ((((float)llRound(C.x * 100.0) / 100.0) == ((float)llRound(D.x * 100.0) / 100.0)) && (((float)llRound(C.y * 100.0) / 100.0) == ((float)llRound(D.y * 100.0) / 100.0)) && (C.z != D.z)));
}
integer closest_shared(vector ref, integer chain)
{
    vector chainpos = (llList2Vector(llGetLinkPrimitiveParams(llList2Integer(lChains, (chain)), [PRIM_POSITION]), 0));
    vector chainrefpos = ( (chainpos) - (ref) );
    integer shared = 0;
    integer whicht = -1;
    vector post;
    integer t = llGetListLength(lTargets);
 
    while(t)
    {
        --t;
        vector thispos = llList2Vector(llGetObjectDetails(llList2Key(lTargets, t), [OBJECT_POS] ), 0);
        integer segments = geom_nofsharedsegments3D(( (thispos) - (ref) ), chainrefpos);
 
        if(shared < segments)
        {
            shared = segments;
            whicht = t;
            post = thispos;
        }
    }
 
    if((!~whicht)) return whicht;
 
    float maxdist = llVecDist(post, chainpos);
    integer ret = -1;
    t = llGetListLength(lTargets);
 
    while(t)
    {
        --t;
        vector thispos = llList2Vector(llGetObjectDetails(llList2Key(lTargets, t), [OBJECT_POS] ), 0);
        float thisdist = llVecDist(thispos, chainpos);
 
        if(thisdist < maxdist)
        {
            if(geom_nofsharedsegments3D(( (thispos) - (ref) ), chainrefpos) >= shared)
            {
                maxdist = thisdist;
                ret = t;
            }
        }
    }
 
    if(!((!~ret))) return ret;
 
    return whicht;
}
integer closest_target(integer chain)
{
    integer ret = -1;
    vector chainpos = (llList2Vector(llGetLinkPrimitiveParams(llList2Integer(lChains, (chain)), [PRIM_POSITION]), 0));
    float closest = -1.0;
    integer n = llGetListLength(lTargets);
 
    while(n)
    {
        --n;
        float distance = llVecDist(llList2Vector(llGetObjectDetails(llList2Key(lTargets, n), [OBJECT_POS] ), 0), chainpos);
 
        if((-1.0 == closest))
        {
            closest = distance;
            ret = n;
        }
        else
        {
            if(distance < closest)
            {
                closest = distance;
                ret = n;
            }
        }
    }
 
    return ret;
}
swap_chains(integer ca, integer cb)
{
    key tmp = llList2Key(lChains, (ca) + 1);
    (lChains = llListReplaceList(lChains, (list)llList2Key(lChains, (cb) + 1), (ca) + 1, (ca) + 1));
    (lChains = llListReplaceList(lChains, (list)tmp, (cb) + 1, (cb) + 1));
}
list find_chain_crossedby(integer chain)
{
    vector start = (llList2Vector(llGetLinkPrimitiveParams(llList2Integer(lChains, (chain)), [PRIM_POSITION]), 0));
    vector end = llList2Vector(llGetObjectDetails(llList2Key(lChains, (chain) + 1), [OBJECT_POS] ), 0);
    integer many = 0;
    integer which = -1;
    integer c = llGetListLength(lChains);
 
    while(c)
    {
        c -= 2;
 
        if((chain != c) && (llList2Key(lChains, (c) + 1) != NULL_KEY))
        {
            vector cpos = (llList2Vector(llGetLinkPrimitiveParams(llList2Integer(lChains, (c)), [PRIM_POSITION]), 0));
            vector cend = llList2Vector(llGetObjectDetails(llList2Key(lChains, (c) + 1), [OBJECT_POS] ), 0);
 
            if(!(boolSegmentEndsAbove(end, cend, start, cpos)))
            {
                if(geom_linesegmentscross(start, end, cpos, cend))
                {
                    ++many;
                    which = c;
                }
            }
        }
    }
 
    return [which, many];
}
hide()
{
    llSay(PUBLIC_CHANNEL, "(" + (string)( (61440 - llGetUsedMemory() ) >> 10) + "kB) ~> " + "removing chains");
    integer _n = llGetListLength(lChains);
 
    while(_n)
    {
        _n -= 2;
        llLinkParticleSystem(llList2Integer(lChains, (_n)), []);
        (lChains = llListReplaceList(lChains, (list)NULL_KEY, (_n) + 1, (_n) + 1));
    }
 
    (status -= 4 * ( !!(status & 4) ) );
}
mkchains()
{
    (status -= 2 * ( !!(status & 2) ) );
    llSetTimerEvent(0.0);
 
    if(!(llGetListLength(lChains) && llGetListLength(lTargets)))
    {
        llSay(PUBLIC_CHANNEL, "(" + (string)( (61440 - llGetUsedMemory() ) >> 10) + "kB) ~> " + "no chains or targets to work with");
        (status -= 1 * ( !!(status & 1) ) );
        return;
    }
 
    llSay(PUBLIC_CHANNEL, "(" + (string)( (61440 - llGetUsedMemory() ) >> 10) + "kB) ~> " + "computing ...");
    vector refc = ZERO_VECTOR;
    integer count = 0;
    integer c = llGetListLength(lChains);
 
    while(c)
    {
        c -= 2;
        refc += (llList2Vector(llGetLinkPrimitiveParams(llList2Integer(lChains, (c)), [PRIM_POSITION]), 0));
        ++count;
    }
 
    refc /= (float)(count + !count);
    c = llGetListLength(lChains);
 
    while(c)
    {
        c -= 2;
        integer t = closest_shared(refc, c);
 
        if(!((!~t)))
        {
            (lChains = llListReplaceList(lChains, (list)llList2Key(lTargets, t), (c) + 1, (c) + 1));
            lTargets = llDeleteSubList(lTargets, t, t);
        }
    }
 
    {
        integer _n = llGetListLength(lChains);
 
        while(_n)
        {
            _n -= 2;
 
            if((llList2Key(lChains, (_n) + 1) != NULL_KEY)) if((llGetUsedMemory() < 61440) && !~llListFindList(lTargets, [llList2Key(lChains, (_n) + 1)]))
                {
                    lTargets += [llList2Key(lChains, (_n) + 1)];
                }
        }
    }
 
    integer tries = llGetListLength(lChains);
    integer swapped;
 
    do
    {
        swapped = FALSE;
        c = llGetListLength(lChains);
 
        while(c)
        {
            c -= 2;
 
            if(!((llList2Key(lChains, (c) + 1) != NULL_KEY))) jump next_chain;
 
            list crosses = find_chain_crossedby(c);
 
            if(!((!~llList2Integer(crosses, 0))))
            {
                swap_chains(c, llList2Integer(crosses, 0));
                swapped = TRUE;
                list more = find_chain_crossedby(c);
 
                if(!(!~llList2Integer(more, 0)) && (llList2Integer(crosses, 1) < llList2Integer(more, 1)))
                {
                    swap_chains(c, llList2Integer(crosses, 0));
                    swapped = FALSE;
                }
            }
 
            integer alternative_target = closest_target(c);
 
            if(!((!~alternative_target)))
            {
                vector chainpos_c = (llList2Vector(llGetLinkPrimitiveParams(llList2Integer(lChains, (c)), [PRIM_POSITION]), 0));
                vector endpos_c = llList2Vector(llGetObjectDetails(llList2Key(lChains, (c) + 1), [OBJECT_POS] ), 0);
                vector endpos_alt = llList2Vector(llGetObjectDetails(llList2Key(lTargets, alternative_target), [OBJECT_POS] ), 0);
 
                if(llVecDist(chainpos_c, endpos_alt) < llVecDist(chainpos_c, endpos_c))
                {
                    integer alternative_chain = llListFindList(lChains, [llList2Key(lTargets, alternative_target)]);
 
                    if(!((!~alternative_chain)))
                    {
                        alternative_chain -= 1;
                        vector chainpos_alt = (llList2Vector(llGetLinkPrimitiveParams(llList2Integer(lChains, (alternative_chain)), [PRIM_POSITION]), 0));
 
                        if(!(boolSegmentEndsAbove(endpos_c, endpos_alt, chainpos_c, chainpos_alt)))
                        {
                            if(geom_linesegmentscross(chainpos_c, endpos_c, chainpos_alt, endpos_alt))
                            {
                                swap_chains(c, alternative_chain);
                                swapped = TRUE;
                            }
                        }
                    }
                    else
                    {
                        vector rel_chainpos = ( (chainpos_c) - (refc) );
 
                        if(geom_nofsharedsegments3D(rel_chainpos, ( (endpos_alt) - (refc) )) >= geom_nofsharedsegments3D(rel_chainpos, ( (endpos_c) - (refc) )))
                        {
                            (lChains = llListReplaceList(lChains, (list)llList2Key(lTargets, alternative_target), (c) + 1, (c) + 1));
                            swapped = TRUE;
                        }
                    }
                }
            }
 
            @next_chain;
        }
 
        --tries;
    }
    while(swapped && tries);
 
    if(tries) llSay(PUBLIC_CHANNEL, "(" + (string)( (61440 - llGetUsedMemory() ) >> 10) + "kB) ~> " + "solution found");
 
    integer _n = llGetListLength(lChains);
 
    while(_n)
    {
        _n -= 2;
 
        if((llList2Key(lChains, (_n) + 1) != NULL_KEY))
        {
            vector color = <llFrand(1.0), llFrand(1.0), llFrand(1.0)>;
            llLinkParticleSystem(llList2Integer(lChains, (_n)), [ PSYS_PART_MAX_AGE, 3.0, PSYS_PART_FLAGS, PSYS_PART_FOLLOW_VELOCITY_MASK | PSYS_PART_TARGET_POS_MASK | PSYS_PART_FOLLOW_SRC_MASK | PSYS_PART_EMISSIVE_MASK, PSYS_PART_START_COLOR, color, PSYS_PART_START_SCALE, <0.2, 0.2, 0.0>, PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_DROP, PSYS_SRC_BURST_RATE, 0.04, PSYS_SRC_ACCEL, < 0.0, 0.0, -1.2 >, PSYS_SRC_BURST_PART_COUNT, 1, PSYS_SRC_BURST_SPEED_MIN, 3.0, PSYS_SRC_BURST_SPEED_MAX, 3.0, PSYS_SRC_TARGET_KEY, llList2Key(lChains, (_n) + 1), PSYS_SRC_MAX_AGE, 0.0, PSYS_SRC_TEXTURE, "4cde01ac-4279-2742-71e1-47ff81cc3529" ]);
        }
    }
 
    (status += 4 * !(status & 4) );
    (status -= 1 * ( !!(status & 1) ) );
    llSay(PUBLIC_CHANNEL, "(" + (string)( (61440 - llGetUsedMemory() ) >> 10) + "kB) ~> " + "done");
}
 
default
{
    listen(integer channel, string name, key other_device, string _MESSAGE)
    {
        if("identify" == _MESSAGE)
        {
            llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice, other_device, "RFEDIP-1.0", "identity"], "|"));
            return;
        }
 
        list payload = llParseString2List((_MESSAGE), ["|"], []);
 
        if(llGetListLength(payload) < 4) return;
 
        if((llList2Key(payload, 0) != other_device) || (llList2Key(payload, 1) != kThisDevice) || !(~llSubStringIndex(llList2String(payload, 2), "RFEDIP") )) return;
 
        string token = llList2String(payload, 3);
 
        if("tether point" == token)
        {
            if((llGetUsedMemory() < 61440) && !~llListFindList(lTargets, [llList2String(payload, 4)]))
            {
                lTargets += [llList2String(payload, 4)];
            }
 
            return;
        }
 
        if("msg_end!" == token)
        {
            if(( !!(status & 2) ) )
            {
                --iDevicesAround;
                iDevicesAround = ( ( (llAbs( (iDevicesAround) >= (0) ) ) * (iDevicesAround) ) + ( (llAbs( (iDevicesAround) < (0) ) ) * (0) ) );
 
                if(!(iDevicesAround)) mkchains();
            }
 
            return;
        }
 
        if("identity" == token)
        {
            ++iDevicesAround;
            llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice, other_device, "RFEDIP-1.0", "tether"], "|"));
            return;
        }
 
        if(( !!(status & 4) ) && (llList2String(payload, 3) == "tether"))
        {
            integer _n = llGetListLength(lChains);
 
            while(_n)
            {
                _n -= 2;
                llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice, other_device, "RFEDIP-1.0", "tether point", llGetLinkKey(llList2Integer(lChains, (_n)))], "|"));
            }
        }
 
        llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice, other_device, "RFEDIP-1.0", "msg_end!", -20131224], "|"));
    }
    touch_start(integer t)
    {
        if(!(llGetListLength(lChains)))
        {
            llSay(PUBLIC_CHANNEL, "(" + (string)( (61440 - llGetUsedMemory() ) >> 10) + "kB) ~> " + "no chains to create");
            return;
        }
 
        if(( !!(status & 1) ) )
        {
            llSay(PUBLIC_CHANNEL, "(" + (string)( (61440 - llGetUsedMemory() ) >> 10) + "kB) ~> " + "busy, please wait");
            return;
        }
 
        if(( !!(status & 4) ) )
        {
            hide();
            return;
        }
 
        (status += 1 * !(status & 1) );
        llSay(PUBLIC_CHANNEL, "(" + (string)( (61440 - llGetUsedMemory() ) >> 10) + "kB) ~> " + "querying for devices");
        llPlaySound("971bc958-ea04-194f-a78a-12826264dae4", 1);
        lTargets = [];
        iDevicesAround = 0;
        llSetTimerEvent(30.0);
        (status += 2 * !(status & 2) );
        llRegionSay(-20131224, "identify");
    }
    timer()
    {
        llSay(PUBLIC_CHANNEL, llDumpList2String(["(", (61440 - llGetUsedMemory() ) >> 10, "kB ) ~>", "timeout,", iDevicesAround, "targets detected"], " "));
        mkchains();
    }
    state_entry()
    {
        status = 0;
        (lChains = getlinknumbersbylistnamedappend_attached_notnamed(["Chain-0", "Chain-1", "Chain-2", "Chain-3", "Chain-4", "Chain-5", "Chain-6", "Chain-7", "Chain-8", "Chain-9", "Chain-10", "Chain-11"]));
        integer _n = llGetListLength(lChains);
 
        while(_n)
        {
            _n -= 2;
            llSetLinkPrimitiveParamsFast(llList2Integer(lChains, (_n)), [PRIM_TEXT, "", <0, 0, 0>, 0.0]);
        }
 
        kThisDevice = llGetLinkKey(llGetLinkNumber());
        llListen(-20131224, "", NULL_KEY, "");
    }
    changed(integer w)
    {
        if(w & CHANGED_LINK)
        {
            hide();
            (lChains = getlinknumbersbylistnamedappend_attached_notnamed(["Chain-0", "Chain-1", "Chain-2", "Chain-3", "Chain-4", "Chain-5", "Chain-6", "Chain-7", "Chain-8", "Chain-9", "Chain-10", "Chain-11"]));
            kThisDevice = llGetLinkKey(llGetLinkNumber());
        }
    }
    on_rez(integer p)
    {
        kThisDevice = llGetLinkKey(llGetLinkNumber());
        hide();
    }
}
</lsl>
 
The reference implementation is available for free on the [https://marketplace.secondlife.com/p/rfedip-reference-implementation/5644685 marketplace].
 
The code on this wiki page is more recent than the code with the example devices because some optimizations have been applied.
 
===References===
 
* [http://gcc.gnu.org/onlinedocs/cpp/Macros.html#Macros Some documentation about cpp macros.]
* [http://forum.unity3d.com/threads/17384-Line-Intersection The function to compute whether line segments intersect or not is taken from here.]
* [http://opengate.ma8p.com/open9.tar.gz The opengate source provides an example of how to use cpp as a preprocessor.]
* [https://marketplace.secondlife.com/p/rfedip-reference-implementation/5644685 Reference Implementation]
 
===History===


* 2013-12-27: initial page created
* 2013-12-27: initial page created
Line 3,319: Line 457:
* 2014-01-04: yet another bugfix
* 2014-01-04: yet another bugfix


* todo: create some wiki as a central place to put information about devices and tokens they use
* 2014-01-13: article rewritten
 
* todo: create some wiki as a central place to put information about devices and tokens they use; update item with reference implementation on marketplace

Revision as of 18:13, 12 January 2014

Ratany fine Engineerings Device Interface Protocol (rfedip)

The Ratany fine Engineering Device Interface Protocol (short: rfedip) is an extensible protocol for the detection of compliant devices and for the handling of the communication between such devices.

The protocol defines how devices can be detected and rules for how the devices can communicate. It does not define the very content of such communications, with the exception of a few so-called control messages.


The protocol has been created because there doesn´t seem to be any sort of standard to detect devices. Devices can be leashing posts, furniture, collars, cuffs and chastity belts as well as any other gadget the design of which involves interoperability or some sort of communication with other devices.

Using sensors to scan for devices has turned out to be an insufficient alternative. The sensor range is inevitably limited, and particular prims --- which may be the actual points of an object to tie a leash to --- of objects cannot be detected with a sensor.

Using existing protocols like the lockmeister protocol also turned out not to be feasible. A device using the lockmeister protocol only becomes active from an agent sitting on the device or touching the device, and the device does itself not listen for queries to identify itself.

At the time of this writing, open collar items seem to employ some sort of protocol and maybe even some sort of API. However, these are not documented, and development appears to be in progress such that significant changes might be implemented.

Other protocols may exist that are vendor-specific. Insofar their designs have not been disclosed, they are unsuited to achieve compatibility and interoperability of devices made by different creators. A device that lacks compatibility with other devices may have no more than a very limited use for the user of the device, especially when the functionality of the device genuinely requires that the device works with others.

The Ratany fine Engineering Device Interface Protocol is intended to encourage and to help with the creation of devices that are compatible with others.


The availability of a useful protocol does not mean that creators of devices will be inclined to use it. Please consider this article as a draft with the intention to create a useful protocol which hopefully might be used by many creators --- and as a request for comments. Please feel free to use the discussion page of this article to add your suggestions and ideas, or to contact me directly.

Editing this Article

This article is written in my favourite editor. When I update the article on the wiki, I edit the whole article, delete everything and paste the new version in. That´s simply the easiest and most efficient way for me.

Unfortunately, this means that your modifications may be lost when I update the article and don´t see them.

Please use the "discussion" page of this article to post contributions or to suggest changes so they can be added to the article.

Requirements brought upon a protocol

The protocol should be:


  • available
  • extensible
  • independent of the medium which is used to transmit protocol messages
  • reliable
  • easy to implement/use
  • compatible with previous versions of the protocol when new versions are created


Specification

In General

The rfedip protcol uses strings of characters that can be transfered as messages of a type which can be sent by functions like llMessageLinked(), llRegionSayTo(), llRegionSay(), llShout(), llSay(), llWhisper() as well as in the body of an email or via the use of the HTTP protocol.

An rfedip message is a string which is divided into fields by the use of a special character as separator. The fields of a message are defined as follows, and they appear in the message in the order they are listed here:


  • sender-UUID: The UUID of the sending device.
  • recipient-UUID: The UUID of the device the message is directed to.
  • protocol version: A string that specifies the version of the protocol.
  • payload: The payload of the protocol. The payload is generally assumed to consist of a token. The token may be followed by a parameter. Multiple tokens and parameters may be sent in a single message. However, a single message should not contain multiple tokens. Multiple messages should be used to avoid the creation of messages that contain multiple tokens.


All devices compliant with the rfedip protocol must have available documentation about all the tokens and, if applicable, the parameters used with the tokens the device supports. The documentation must include the tokens and parameters the device understands, as well as the tokens and parameters it might use itself to communicate with other rfedip-compliant devices.

This is to ensure that creators and users of rfedip-compliant devices can refer to the documentation of devices created by others and make the devices they are creating compatible with devices created by others, if they so choose.

A device for which such documentation is not freely available cannot be considered as compliant or compatible with the rfedip protocol.


Definitions

The rfedip protocol itself defines:


  • a string specifying the protocol version: "RFEDIP-1.0"
  • that future versions of the protocol must maintain compatibility with previous versions of the protocol
  • a suggestion as to what may be considered as "sufficient version": "RFEDIP" (What is considered as "sufficient version" for a particular device is up to the creator of that device.)
  • the special character that must be used to seperate a string of characters into fields in order to form a protocol message: "|"
  • a default communications channel, when applicable: -20131224
  • supporting email and http as transport media for protocol messages is optional; supporting the default communications channel is mandatory
  • a token that designates a request for a device to identify itself: "identify"
  • a token that designates a protocol message a device identifies itself with: "identity"
  • a token that designates a protocol message signalling that a communication (sequence) has ended, mandatorily followed by the channel in question: "msg_end!"
  • a token that designates a protocol message requesting that a particular channel, if applicable, be used for further communication, mandatorily followed by the channel which is to be used as a parameter: "openchan"
  • that a particular channel which was opened can be closed when the device that requested opening this channel sends the protocol message that signals that a communication (sequence) has ended
  • that a particular channel which was opened on request of a device can unconditionally be closed one hour after the last communication has occurred on that channel
  • that identification requests must always be answered (consequently, the default channel must never be closed, even not when a device signals the end of a communication (sequence) for the default channel)
  • that messages involving tokens as defined here must be sent on the default channel for they are considered control messages (i. e. messages like identification requests, responses to those, opening channels, signalling the end of a communication (sequence))
  • that devices should respond with the protocol message indicating the end of communication for tokens they do not understand to potentially save other devices unnesessary waiting times


Reference Implementation: A Tethering Device

The tethering device can be queried for the UUIDs of prims to which chains or leashes can be attached. The token to query the device is "tether". The device replies with the token "tether point", followed by the UUID of a chaining point. Each chaining point is reported in a seperate response message, one chaining point per message. When all chaining points have been reported, this devices sends a RFEDIP_protEND control message.


<lsl> list getlinknumbersbyname(string name) { integer n = llGetObjectPrimCount(llGetLinkKey(llGetLinkNumber())); list numbers = []; name = llToLower(name);

if(n > 1) { while(n) { if((~llSubStringIndex(llToLower(llList2String(llGetLinkPrimitiveParams( n, [PRIM_NAME]), 0)), name))) { numbers += n; }

--n; } } else { if((~llSubStringIndex(llToLower(llGetObjectName()), name))) { numbers += n; } }

return numbers; }

key kThisDevice; list lHooks;

default { state_entry() { // remember the link numbers of the chain targets // lHooks = getlinknumbersbyname("hook");

if(!(llGetListLength(lHooks))) { lHooks = (list)llGetLinkNumber(); }

kThisDevice = llGetLinkKey(llGetLinkNumber()); llListen(-20131224, "", NULL_KEY, ""); }

changed(integer w) { // maybe do something else here if you need to sit on // this device // if(w & CHANGED_LINK) { kThisDevice = llGetLinkKey(llGetLinkNumber()); lHooks = getlinknumbersbyname("hook");

if(!(llGetListLength(lHooks))) { lHooks = (list)llGetLinkNumber(); } } }

listen(integer channel, string name, key other_device, string _MESSAGE) { if("identify" == _MESSAGE) { // Indistinctively answer queries that want to // detect this device: The answer goes to the // sender (i. e. other_device) and looks like: // // "<sender-uuid>|<recipient-uuid>|RFEDIP_sVERSION|RFEDIP_protIDENTIFY" // // For the device that receives the answer to // the request to identify, the <sender-uuid> // is the UUID of this device. // llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice, other_device, "RFEDIP-1.0", "identity"], "|")); return; }

// From here on, received messages are expected to // look like: // // "<sender-uuid>|<recipient-uuid>|RFEDIP_sVERSION|<token>[|parameter|parameter...]" // // <sender-uuid> is the UUID of the device sending the // message (i. e. other_device); <recipient-uuid> is // the UUID of the recipient, i. e. of this device // // Please do not confuse incoming messages with // outgoing messages!

// The received message is put into a list for further // processing: // list payload = llParseString2List((_MESSAGE), ["|"], []);

// Attempt to verify whether the message looks valid: // if(llGetListLength(payload) < 4) { return; }

// Attempt to make sure that the message is for this // device: // // + ignore messages that appear not to be sent by the // device they claim to be sent from by verifying // the sender given in the message with the actual // sender // // + ignore messages that appear not be destined for // this device by verifying the recipient given in // the message with what this device actually is // // + ignore messages that request a different version // of the protocol by verifying the protocol version // given in the message --- in this example, a check // for what is considered a "sufficient version" is // applied // if((llList2Key(payload, 0) != other_device) || (llList2Key(payload, 1) != kThisDevice) || !(~llSubStringIndex(llList2String(payload, 2), "RFEDIP"))) { return; }

// From here on, the capabilities of the device can be // implemented.

// Here: Report the chain target points and send of of // communication message. // if(llList2String(payload, 3) == "tether") { integer n = llGetListLength(lHooks);

while(n) { --n; llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice, other_device, "RFEDIP-1.0", "tether point", llGetLinkKey(llList2Integer(lHooks, n))], "|")); } }

// indicate end of communication when all points have // been reported or when messages have been received // this device doesn´t support // llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice, other_device, "RFEDIP-1.0", "msg_end!", -20131224], "|")); }

on_rez(integer p) { // UUID changes when rezzed // kThisDevice = llGetLinkKey(llGetLinkNumber()); } } </lsl>


You can find the source for this device in the git repository and further documentation in this article.


Reference Implementation: A device using chaining points

This device can send queries for the UUIDs of prims to which chains can be attached. The token used for the query is "tether". This device expects answers to this query to use the token "tether point", followed by the UUID of the point as parameter. It expects devices that have received a query to indicate that all their UUUIDs have been reported by sending a a RFEDIP_protEND control message, regardless whether any points have been reported or not.

This device can be queried for the UUIDs of prims to which chains can be attached. The token to query the device is "tether". The device replies with the token "tether point", followed by the UUID of a chaining point. Each chaining point is reported in a seperate response message, one chaining point per message. When all chaining points have been reported, this devices sends a RFEDIP_protEND control message.

When this device has not created chains itself, it does not report its chain target points but sends a RFEDIP_protEND control message instead.


The implementation happened to grow quite a bit larger than what I had expected. What I expected is that it is simple to link a couple chains from an object to a couple points that are somewhere around. Pfff!

It is simple to link each chain to the point closest to it. When you do that, the result looks retarded in most cases because the chains may get entangled or go across the object, and several chains may go to the same point. That was ridiculous and not acceptable.

Humans solve the problem without thinking. For example, they tie their boats to the tethering points around the berths their boats are in without entangling all the ropes. They do not attach the ropes in such a way that they go across the boat. Humans are flexible and sometimes cross over some ropes or have multiple ropes go to the same point when the conditions let this appear to be advisable. The result never looks retarded because the way it´s done makes sense.

Even children do it right. I couldn´t figure out how I would do it because I would just do it. People I asked didn´t know how to do it, either. The problem is too simple. This made it surprisingly difficult to create an algorithm which yields results that don´t look retarded for at least most conditions. It took a lot of experimentation and was fun to create.

What this algorithm does, and how it does it, is irrelevant for the purpose of this article. I´ll only put the more relevant part into the article. You can find the whole source in the git repository and further documentation in this article.


<lsl> default { listen(integer channel, string name, key other_device, string _MESSAGE) { // // This first part is the same as with the tethering device. //

if("identify" == _MESSAGE) { llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice, other_device, "RFEDIP-1.0", "identity"], "|")); return; }

list payload = llParseString2List((_MESSAGE), ["|"], []);

if(llGetListLength(payload) < 4) { return; }

if((llList2Key(payload, 0) != other_device) || (llList2Key(payload, 1) != kThisDevice) || !(~llSubStringIndex(llList2String(payload, 2), "RFEDIP"))) { return; }

string token = llList2String(payload, 3);

// // The rest is device specific. Tokens in received // protocol messages trigger various actions to make // the device to its job. //

if("tether point" == token) { // [...] }

if("msg_end!" == token) { // when all devices have indicated end of communication, // create the chains // if((!!(status & 2))) { --iDevicesAround; iDevicesAround = (((llAbs((iDevicesAround) >= (0))) * (iDevicesAround)) + (( llAbs((iDevicesAround) < (0))) * (0)));

if(!(iDevicesAround)) { mkchains(); } }

return; }

if("identity" == token) { // when other devices identify themselves, query them for tether points // ++iDevicesAround; llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice, other_device, "RFEDIP-1.0", "tether"], "|")); return; }

if((!!(status & 4)) && (llList2String(payload, 3) == "tether")) { // answer queries from other devices when this device is chained // integer _n = llGetListLength(lChains);

while(_n) { _n -= 2; llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice, other_device, "RFEDIP-1.0", "tether point", llGetLinkKey(llList2Integer(lChains, (_n)))], "|")); } }

llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice, other_device, "RFEDIP-1.0", "msg_end!", -20131224], "|")); }

touch_start(integer t) { // [...] iDevicesAround = 0; // [...]

// ask other devices to identify // llRegionSay(-20131224, "identify"); }

// [...] } </lsl>


You can find the source for this device in the git repository and further documentation in this article.

The reference implementation is available with some example objects for free on the marketplace.

The code on this wiki page is more recent than the code with the example devices because some optimizations have been applied.


References

Reference Implementation

History=

  • 2013-12-27: initial page created
  • 2013-12-27: tokens changed to something more easily readable; different indentation used for LSL script
  • 2013-12-27: tried to improve the "About" section
  • 2013-12-29: added second example
  • 2013-12-31: replaced second example with an improved version; added suggestion to send an end-of-comminucation message for tokens not understood
  • 2013-12-31: added reference to opengate source; removed unnecessary return statement from first example
  • 2014-01-04: added reference implementation
  • 2014-01-04: made reference implementation available on the marketplace
  • 2014-01-04: the code on this page was slightly optimized
  • 2014-01-04: bugfix, sigh ...
  • 2014-01-04: use PRIM_POSITION instead of PRIM_POS_LOCAL
  • 2014-01-04: yet another bugfix
  • 2014-01-13: article rewritten
  • todo: create some wiki as a central place to put information about devices and tokens they use; update item with reference implementation on marketplace