blog:opensim:scripts:ircbridge_network
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revision | |||
blog:opensim:scripts:ircbridge_network [2025/06/25 14:22] – removed - external edit (Unknown date) 127.0.0.1 | blog:opensim:scripts:ircbridge_network [2025/06/25 14:22] (current) – ↷ Page moved from blog:opensim:ircbridge_network to blog:opensim:scripts:ircbridge_network Phil Ide | ||
---|---|---|---|
Line 1: | Line 1: | ||
+ | ====== IRCBridgeModule Networking ====== | ||
+ | [{{: | ||
+ | |||
+ | The first thing you have to know about the module, is that communications using this bridge are grid agnostic - that is, it doesn' | ||
+ | |||
+ | There is a video overview of the scripts discussed below, showing you how it works and how simple it is to use. Hopefully it will ignite ideas on what you can do with it. [[https:// | ||
+ | |||
+ | In the image at the top of this article, you can see an example of inter-region communications at work. The master - the object you see in the image - sends out a request for information from objects in other regions. Their response is the number of agents (account-based avatars) and NPCs (non-account-based avatars) in each region. It takes less than a second from sending out the request to receiving the last response. In fact, I have trouble releasing the mouse-button before all the information is returned (touching the board sends out the request). As you can see, the list includes most of my regions in mossgrid and two from OSGrid. The fact that some regions are in another grid has no relationship with how long a response takes to arrive back to the display board. | ||
+ | ===== Other Options ===== | ||
+ | |||
+ | Objects (prims) have other options for communicating with each other. If they' | ||
+ | |||
+ | You can also communicate via http, where an object acquires a URL from the region server, and the other object sends messages to that URL. In effect, it becomes a web server. This requires the sending prim to know the receiving prim's URL in advance. URL's will change when the region server is restarted and when the prim's scripts are reset or when the prim is copied or re-rezzed. Therefore, an external database needs to store the current URL so the sending prim can fetch the address when it needs it. | ||
+ | |||
+ | There are other methods too (e.g. prim email), but only HTTP and IRC are grid agnostic. If you don't control the simulators for all your regions, HTTP may be your only option, otherwise IRC is an excellent choice. Note too the caveat about allowing regions to contact regions on the same LAN via http: OpenSimulator determined that this represented a serious security flaw, and it is disallowed by default. It can be enabled easily enough, but you should know what the risks are and how to mitigate them. | ||
+ | |||
+ | ===== Peer-Peer ===== | ||
+ | |||
+ | Let me clarify what I'm doing here: IRC is used as a transport mechanism. The script I have written will create a peer-to-peer network whose use is simplified by a node/ | ||
+ | |||
+ | Any node can be a sub-node of another node (which in turn might also be a sub-node). To define nodes and groups of nodes in a sub-node hierarchy, I have implemented an inverse domain style system. Within their defined group (the list of parent groups), a node name must be unique. | ||
+ | <code lsl2> | ||
+ | nodeNameA = " | ||
+ | nodeNameB = " | ||
+ | nodeNameC = " | ||
+ | nodeNameD = " | ||
+ | nodeNameA = " | ||
+ | </ | ||
+ | |||
+ | This is referred to as the node address, and it is used to target messages at specific nodes or groups of nodes - e.g. using the above example: | ||
+ | |||
+ | <code lsl2> | ||
+ | addressA = " | ||
+ | addressB = " | ||
+ | addressC = " | ||
+ | </ | ||
+ | |||
+ | The node address is placed in the object' | ||
+ | |||
+ | Since this is a peer-to-peer network, there is no central server to ensure a node (object) has a unique address, so the nodes determine this themselves. When a node is rezzed, it first sends out a '' | ||
+ | |||
+ | If another node already exists with the same address, it sends back a '' | ||
+ | |||
+ | All this happens magically without any action required by the owner. | ||
+ | |||
+ | ===== First-Class Messages ===== | ||
+ | |||
+ | Each message sent is a first-class message. This means there is no response required - if it was then a response would require a response and quickly flood the system. Therefore, sending a response to a message is entirely optional, and any response sent is also a first-class message. | ||
+ | |||
+ | First-class messaging also means that the sender doesn' | ||
+ | |||
+ | |||
+ | ===== Subdomains ===== | ||
+ | The idea of having subdomains is to group things together. As an example, let's take a vending system. You have vendors in all your regions, and for some strange reason known only to yourself, they are replicated several times. This is after all, an example, and although it may not match a real-world scenario, it is easy to understand. | ||
+ | |||
+ | You have several table vendors, several chair vendors etc. Let's say you have a new table to add, or a script is updated in a chair and want to replace the existing one in the vendor. If the objects you are ' | ||
+ | |||
+ | A centralised vending system is simpler. A central vend-server has all the items in its inventory, and the vendors themselves get images of the items for sale (the texture UUIDs) from the vend-server. The vendor needs only display the images to avatars and set the price, and a good vending system is capable of displaying a user-controlled slideshow of the images, effectively selling multiple items. | ||
+ | |||
+ | When someone buys something, the vendor collect the money (if any) and passes the item and avatar information to the vend-server, | ||
+ | |||
+ | //Note: in a system where in-world currency is involved, the vendors will perform the transaction and only after confirming the avatar has paid in full will it contact the vend-server. Further, for security reasons, the vendor should contact the vend-server via https rather than IRCBridge. IRCBridge is useful for communications from the vend-server to the vendors though, especially when updating inventory. However, using IRCBridge (and this domain-based system) will allow you to fetch the URL of the vend-server without having to register the URL on an external database.// | ||
+ | |||
+ | Your vending system might have these subdomains: | ||
+ | - vend.furniture.tables | ||
+ | - vend.furniture.chairs | ||
+ | - vend.furniture.sofas | ||
+ | - vend.furniture.beds | ||
+ | - vend.houses.mansions | ||
+ | - vend.houses.castles | ||
+ | |||
+ | With the above system you can add a new table to the server, then send a message to the vendors to update their list of textures (images of the items to display). If you only had one table vendor it would probably be called '' | ||
+ | |||
+ | Using standard object-object communications, | ||
+ | <code lsl2> | ||
+ | // options are [ to_region, to_address ] | ||
+ | list defaultOptions = [ " | ||
+ | |||
+ | // send a single message to all the table vendors | ||
+ | string item_price = " | ||
+ | string msg_to_send = " | ||
+ | |||
+ | relay( msg_to_send, | ||
+ | </ | ||
+ | |||
+ | This sends a single message to the IRCBridge, which then echoes it to each connected region. Each node receives the message and determines whether it is meant for them. If it is, it passes the message to a handler script via a '' | ||
+ | |||
+ | <code lsl2> | ||
+ | // defined at the top of your code | ||
+ | integer LNK_MSG_RECV = -4247894; | ||
+ | integer LNK_MSG_SEND = -4247895; | ||
+ | |||
+ | // options are [ to_region, to_address ] | ||
+ | list defaultOptions = [ " | ||
+ | |||
+ | // This is what relay looks like in your own code | ||
+ | relay( string message, list who ){ | ||
+ | if( who == [] ){ | ||
+ | who = defaultOptions; | ||
+ | } | ||
+ | if( llGetListLength( who ) == 1 ){ | ||
+ | who = [" | ||
+ | } | ||
+ | llMessageLinked( LINK_THIS, LNK_MSG_SEND, | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Obviously, if you target a specific object by its unique address, only that object will respond: | ||
+ | <code lsl2> | ||
+ | // send a single message to a specific prim | ||
+ | string msg_to_send = "blah blah blah"; | ||
+ | |||
+ | relay( msg_to_send, | ||
+ | </ | ||
+ | |||
+ | We saw earlier that '' | ||
+ | <code lsl2> | ||
+ | // send a simple message to all table vendors in the region " | ||
+ | string msg_to_send = "blah blah blah"; | ||
+ | |||
+ | relay( msg_to_send, | ||
+ | </ | ||
+ | |||
+ | Going back to selecting groups of objects, we can also select a higher level group. Sending to '' | ||
+ | |||
+ | It's worth noting here that if all the groups mentioned above existed, and there was a node named '' | ||
+ | |||
+ | ===== Black Box ===== | ||
+ | |||
+ | There are two scripts that come with this system. There is a core script which handles all the communications and filtering. This works like a black box - you don't need to edit it! All you need to know is how to send a message to it so that it will transmit that message for you, and how to receive any inbound messages intended for your object. | ||
+ | |||
+ | The other script is an example of how to send and receive messages from the IRC network - the handler script. | ||
+ | ===== Message Types ===== | ||
+ | |||
+ | Note that the messages we've sent in the above examples has the format '' | ||
+ | |||
+ | <code lsl2> | ||
+ | string message_type = " | ||
+ | string message_name = " | ||
+ | string data_payload = " | ||
+ | |||
+ | string message = message_type + "::" | ||
+ | </ | ||
+ | |||
+ | This allows you to define message groups: | ||
+ | <code lsl2> | ||
+ | string m1 = " | ||
+ | string m2 = " | ||
+ | string m3 = " | ||
+ | string m4 = " | ||
+ | string m5 = " | ||
+ | |||
+ | </ | ||
+ | |||
+ | The purpose of grouping messages into types is to simplify the code you write to handle such messages. If your needs are pretty simple anyway, then you can just use '' | ||
+ | |||
+ | In my experience, it hasn't been necessary to define a message type, as the message itself and the addresses it is being sent to are sufficient to ensure there is no ambiguity. However, the message type option is available to you and should future-proof the code. | ||
+ | ===== Code ===== | ||
+ | In addition to the core script, a user needs a script to receive inbound messages, handle them and provide responses if required. It receives these messages via '' | ||
+ | |||
+ | Responses to an inbound message should normally be targeted at the sender. | ||
+ | |||
+ | Messages to send are formatted like this: | ||
+ | <code lsl2> | ||
+ | integer AGENT_AGENT = 0; | ||
+ | integer AGENT_NPC | ||
+ | integer AGENT_ALL | ||
+ | |||
+ | // format a message as MSG_TYPE:: | ||
+ | // this message requests a count of agents | ||
+ | myMessage = " | ||
+ | </ | ||
+ | |||
+ | The '' | ||
+ | |||
+ | You don't have to send the data yourself, just pass it to the core script via a link message: | ||
+ | <code lsl2> | ||
+ | // options are [ to_region, to_address ] | ||
+ | list defaultOptions = [ " | ||
+ | |||
+ | // message is in the format described above. | ||
+ | relay( string message, list who ){ | ||
+ | if( who == [] ){ | ||
+ | who = defaultOptions; | ||
+ | } | ||
+ | if( llGetListLength( who ) == 1 ){ | ||
+ | who = [" | ||
+ | } | ||
+ | llMessageLinked( LINK_THIS, LNK_MSG_SEND, | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | The scripts are configured via items in the object' | ||
+ | |||
+ | <code lsl2> | ||
+ | // Settings list from the IRC-DataStore script | ||
+ | list secrets = [ | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | ]; | ||
+ | |||
+ | </ | ||
+ | Once edited, drop the script into the object. It will set the values in the object, report the number of entries it has created and delete itself. There is another script that you can drop into your object and when the owner touches the object it will list all the keys and values in the data store (except those stored with protection). | ||
+ | |||
+ | Data store key/value pairs persist after a region or simulator restart, and if you take the object into inventory and then rez it again, its data store is intact. However, if you copy an object (in-world or in your inventory), the copy doesn' | ||
+ | |||
+ | You'll notice when you look at the scripts, they don't store these values in protected mode. If you want them stored protected then you have the scripts and it's just a simple edit - remember to edit the core script too, it'll need to know it has to fetch protected data store items, and how. | ||
+ | |||
+ | All your other code to generate outgoing messages and to handle incoming messages (arriving via '' | ||
+ | |||
+ | <code lsl2> | ||
+ | integer LNK_MSG_RECV | ||
+ | integer LNK_MSG_SEND | ||
+ | integer LNK_MSG_RESET = -4247896; // reset core script | ||
+ | |||
+ | // msg sections | ||
+ | integer MSG_TYPE = 0; | ||
+ | integer MSG_DATA = 1; | ||
+ | |||
+ | list defaultOptions = [" | ||
+ | |||
+ | // if this is a client prim that only responds to requests and doesn' | ||
+ | // then we can keep this function simple | ||
+ | // | ||
+ | relay( string message, list who ){ | ||
+ | if( who == [] ){ | ||
+ | who = defaultOptions; | ||
+ | } | ||
+ | if( llGetListLength( who ) == 1 ){ | ||
+ | who = [" | ||
+ | } | ||
+ | llMessageLinked( LINK_THIS, LNK_MSG_SEND, | ||
+ | } | ||
+ | |||
+ | // senderDetails is a CSV string " | ||
+ | // | ||
+ | handleMessage( string msg, list senderDetails ){ | ||
+ | // msg = " | ||
+ | // senderDetails = [from-region, | ||
+ | list details = llParseStringKeepNulls( msg, ["::" | ||
+ | string msgType = llList2String( details, MSG_TYPE ); | ||
+ | string msg = llList2String( details, MSG_MSG ); | ||
+ | list data = llParseStringKeepNulls( msg, ";;", | ||
+ | msg = llList2String( data, 0 ); | ||
+ | | ||
+ | // REQ_REGION_INFO is something you have defined | ||
+ | if( msg == " | ||
+ | // do something interesting | ||
+ | // blah-di-blah | ||
+ | | ||
+ | // requires response so reply to sender | ||
+ | // using the sender details targets the specific object that sent the message | ||
+ | relay( msgType+":: | ||
+ | } | ||
+ | } | ||
+ | |||
+ | default { | ||
+ | state_entry() { | ||
+ | // reset irc-handler as last part of startup | ||
+ | llMessageLinked( LINK_THIS, LNK_MSG_RESET, | ||
+ | } | ||
+ | link_message(integer sender_num, integer num, string msg, key id){ | ||
+ | if( num == LNK_MSG_RECV ){ | ||
+ | // incoming message intended for us, so send to handler | ||
+ | handleMessage( msg, (string)id ); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | ===== Gotchas ===== | ||
+ | |||
+ | There are a few gotchas with this. | ||
+ | * The '' | ||
+ | <code ini> | ||
+ | msgformat = " | ||
+ | </ | ||
+ | * If you have duplicate region names, scripts may respond to messages aimed at 'the other' region with the same name. The solution here would be to ensure node names are actually unique and target nodes. | ||
+ | * All messages go to all scripts in all regions connected via the IRCBridgeModule and listening on the inbound channel, so a constant stream of messages is going to have an impact on simulator performance (and therefore affecting all regions supported by that simulator). For this reason, you should limit the number of messages sent. Scripts that filter out messages still consume server resources, but thankfully they are efficient at filtering. | ||
+ | * If you only have one region, you can send messages to groups of objects in exactly the same way using an '' | ||
+ | * In IRC, you don't hear yourself. That means a region that sends a message doesn' | ||
+ | |||
+ | ===== Examples ===== | ||
+ | |||
+ | It's probably a good idea at this point to show some basic examples of usage. These aren't code examples, rather descriptions of scenarios. | ||
+ | |||
+ | ==== Vending System ==== | ||
+ | I've already mentioned a vending system, but I've added it here in case someone decided TL;DR and skipped to the example uses. | ||
+ | ==== Head Counter ==== | ||
+ | If you spend a lot of time scripting or building in one region (as I do in my sandbox), you might want to know how many avatars are in your other regions. | ||
+ | |||
+ | One of the examples in the repository is a headcount system. There is a ' | ||
+ | < | ||
+ | hText = " | ||
+ | </ | ||
+ | |||
+ | To run this example, you must own the land/ | ||
+ | |||
+ | This is a fairly useless example, in that it doesn' | ||
+ | |||
+ | ==== NPC Controller ==== | ||
+ | |||
+ | It is possible to control NPCs with '' | ||
+ | |||
+ | Even more fun, is to turn an NPC into a chat-bot, but take the ' | ||
+ | |||
+ | If you have multiple regions that are adjacent to each other, running on different simulators or simulator instances (for performance reasons), you might want to rez all the NPCs on the region and all adjacent regions at the same time when someone enters the ' | ||
+ | |||
+ | ==== Messaging ==== | ||
+ | By messaging, I mean sending postcards - not notecards, these are different. With a postcard, an avatar touches a postbox which then displays to the avatar a text box for them to fill out their message. The message is sent to the post office, which creates a notecard using the avatar name and adds the message itself to the notecard contents, along with some header information about when it was sent, which region and grid it was sent from. Each of the postboxes are then updated to display the total number of unread postcards. | ||
+ | |||
+ | Since it is only data that is transmitted, | ||
+ | |||
+ | There is a working example of this in the github repository (see links below) | ||
+ | ====== Conclusion ====== | ||
+ | |||
+ | This set of scripts is a tidy solution to using the IRCBridgeModule in a highly effective and dynamic way. It provides extremely fast communications between many objects irrespective of the grid they are on, and the objects themselves are not responsible for ensuring their communication channel is maintained and available, or registered on external websites. | ||
+ | |||
+ | It doesn' | ||
+ | |||
+ | ====== Getting the Source ====== | ||
+ | |||
+ | The source is available on GitHub at [[https:// | ||
+ | |||
+ | The code in this wiki should be taken as a guide only because this page may not be updated alongside the actual code. See the github repository for updated examples and code. | ||
+ | |||
+ | ~~socialite~~ | ||
+ | ~~DISCUSSION~~ | ||