Context: IPC in SL
Overhead, in the form of unnecessary calls to link_message()
handlers, is the primary weakness of the original Companion design, an issue that was only partly mitigated by the 8.5 _whip kernel architecture. To distinguish which messages were intended for which programs, Companion used the link_message()
event's integer parameter, which could only be identified within LSL code. This meant that whenever llMessageLinked()
was used to target a given prim, all scripts in that prim capable of processing any link messages would simultaneously start processing. As most messages are meant for communication between one source and one recipient, the vast majority of these activations were completely unproductive.
In Companion 8.5, a primitive kernel was introduced, named Whip, which kept a table of message numbers understood by each program, and was responsible for starting and stopping programs to prevent them from receiving messages while they were not working. As this was a retrofit, however, many system components could never be stopped (leading to some guaranteed wasted activations on every message). Additionally, programs that happened to witness irrelevant messages while they were waiting for some other task to complete still had to discard them. All of this resulted in a lot of overhead—updating each system module to use the new format was complicated and error-prone, and the signals sent to the kernel for starting or stopping a program were also link messages. In theory, this still put a cap on how many spurious activations could occur, but in practice, the Whip kernel design never achieved any of the goals they set out to accomplish.
As an aside, it may be noted that link message numbers are the standard method of interprocess communication in LSL projects, and the creation of spurious activations, being as it grows quadratically with respect to the number of scripts, cripples their ability to grow beyond a certain size, as sim owners find the overhead of such products objectionable. More than one brand has moved their processing into off-grid servers, leaving their users without contingencies in the event that the brand becomes defunct. Somewhat fewer are the cases that attempt to work around the issue, such as Wendy Starfall's Peanut No. 9 (formerly, OpenCollar Six), which attempts to compartmentalize functions into different links. This is a largely successful approach in practice, but comes with the caveat that all plug-ins are still kept in one link.
The original goal of the ARES project was to determine the viability of using chat channels to perform interprocess communication. LSL tasks are capable of sending and receiving chat messages to any link except their own, and, unlike with link messages, they must explicitly opt into receiving them. Moreover, they may specify that they only wish to receive chat messages from a specific UUID. All of this filtering is handled by the simulator, without invoking the Mono VM, making it much more performant than link messages. This approach is not without its limitations, of course—mainly, that chat channels can only carry strings up to 1024 bytes in length, but modern LSL provides alternatives for that, too.
Companion 8.0 | Companion 8.5 | ARES | |
---|---|---|---|
Introduced | 2014 | 2018 | 2023 |
Message Passing | link messages | chat channels | |
Kernel | none | Whip | Psyche/CX |
Compartments | 2 (system and program) | 3 (kernel, daemon, and program) | |
Delegates | n/a | songbird | repair (ring 2), proc (ring 3) |
Link messages are still used in ARES in a limited role: messages to the kernel are dispatched as linked messages to the kernel link, where they can also be intercepted by the monitor program for logging purposes. They are also used by the kernel delegates to trap system restarts. Delegates are the system modules outside of kernel space responsible for starting and restarting other binaries in their link. In the table below, each link other than those containing a kernel requires a delegate.
Companion | ARES |
---|---|
System Memory (Link 1) Kernel Services Libraries Interface | Kernel Ring (Link 1) Kernel(s) System Monitor |
Daemon Ring (Link 2) Services | |
Program Ring (Link 3) Libraries Applications Interface | |
Program Memory (Link varies) Applications |
ARES Message Format
The idealized message between two programs in ARES contains up to four fields:
aabbccpayload
The first four fields are numbers in the range 0x0000-0x0fff (0-4095), encoded in a cheap base-64 format and left-padded, using the following macros:
#define ares_encode(_number) llChar( ((_number & 0x0fc0) >> 6) + 33 ) + llChar( (_number & 0x3f) + 33 )
#define ares_decode(_code) (((llOrd(_code, 0) - 33) << 6) + (llOrd(_code, 1) - 33))
The fields have the following meanings:
aa
indicates the message type. This is one of the SIGNAL_* constants defined in the ARES/a header.bb
indicates the recipient process identifier (PID). These values are assigned by the kernel when it initializes, in the range 17-4093. Daemons and delegates have reserved constant values outside this range.cc
indicates the source process identifier (PID). This is the PID of the program sending the message.payload
is a string intended for interpretation by the target application. Its format is largely dependent on the particulars of the message type at hand.
As stated, this is only a formal description—ARES seldom uses a full message with all four fields in this encoding. Messages sent to the kernel, for example, represent the aa
message type code using the link message numeric parameter, and the bb
target PID is never sent to programs, except when it is being assigned, as they already know that information.
Linkset Data in ARES
The linkset datastore (LSD) is a flat key/value database accessible synchronously (i.e., without waiting for a reply event) by all scripts within a linkset. It can contain up to 128 kilobytes, and was added to Second Life in late 2022. Its introduction eliminated most use cases for so-called "memory" scripts that serve only to hold information for use in other programs, including the configuration manager (_balance) in Companion.
Settings in LSD
Many of the most glaring bugs in Companion 8.6 were the result of issues relating to external settings storage. To improve idle performance (see IPC in SL), programs would suspend execution when not in use, during which time they would lose their internal state whenever being rezzed (i.e., whenever the controller was attached) or being transferred to a new region. The single-point storage solution for this, _balance, a non-hibernating module, was very performant under ideal conditions, but often dropped data or suffered catastrophic data loss.
The linkset datastore is non-volatile, and can be read quickly and painlessly whenever a script initializes, making it the ideal solution to this problem. As with _balance, each entry in the datastore of this type is organized into a hierarchical JSON object (explained further in Operating ARES: Settings Storage) for the sake of compactness.
LSD Settings
JSON objects stored in LSD are called sections in ARES terminology, to distinguish them from JSON key-value pairs. They are usually stored without any name prefix (e.g. the interface LSD key contains information related to the _interface daemon) but may be referred to with the namespace prefix LSD: in documentation (e.g., LSD:interface) to establish more clearly that the subject being discussed is an item in the linkset datastore and not merely a file with a similar name.
Keys nested within a section are concatenated with a dot; for example, LSD:interface.sound.volume refers to a key-value pair named volume inside an object called sound that is stored within the interface section of the linkset datastore. Dot notation even applies to entries in JSON arrays, so the unit's primary lighting color setting is stored in a setting called LSD:id.color.0 (in Javascript this would be id.color[0])
section | contents |
---|---|
announcer | UUIDs of sounds to use for announcer voice |
ax | trusted sources for downloading packages; currently available upgrades |
badge | UUIDs of graphics to display as the badge for various models |
baseband | configuration for the _baseband (network services) daemon; see Baseband Networking |
chassis | miscellaneous power costs and metrics |
chime | UUIDs of sounds to use for startup and shutdown schemes |
device | currently connected devices |
env | shell environment variables (see Operating ARES: Scripting ARES) |
filter | configuration options for chat processors from the filter program (see Tutorial: Chat: Manipulating chat messages) |
font | texture UUIDs and metrics for VariaType fonts (_variatype daemon) |
fs | definitions for filesystem views, remote sources, and file type associations |
fs:local | list of all local files (array) |
fs:root | table of all files and their corresponding sources |
other entries beginning with fs: | lists of files for each view and remote source (array) |
hardware | miscellaneous settings stored by devices |
help | index of all entries in available help files (*.info) |
id | identification settings (see Operating ARES: Identity) |
input | options for _input and _exec, including aliases and device commands |
interface | options for the _interface daemon and other UI components; see Operating ARES: Interface Customization |
io | permanent settings related to pipes (_io daemon) |
kernel | kernel build info (immutable) |
entries beginning with m: | individual menus |
mantra | settings for mantra hypnosis program |
mastodon | settings for toot Mastodon client |
menu | settings for menu system, mostly state information for sessions |
nav | settings for nav system, including current status |
news | settings for news application |
persona | current persona definition |
pkg | version numbers of available and installed packages |
policy | see Operating ARES: Policies |
power | current subsystem definitions |
repair | combat settings |
restraint | settings for the Restraint RLV relay |
security | see Operating ARES: Security |
sexuality | settings for the Sexuality system extension |
status | homeostasis; both read and written by _status daemon |
swatch | list of individual preset colors |
trigger | trigger words used to activate commands (_baseband daemon; see Baseband Networking) |
vocalization | sound effect UUIDs played by Sexuality during interaction |
vox | current configuration of the chat filter pipeline (see Operating ARES: Input) |
warrior | settings for the Warrior system extension |
Pipes in LSD
LSD also has the useful property that entries have no individual size cap; they may be as large as the script creating them can handle, provided there is room in the datastore. Because of this, they are also excellent for message passing, and there are event handlers to support this programming style in LSL, although hooking these events will inevitably present the same problem as using link messages exclusively for IPC (namely, that they are fired indiscriminately.) Instead, ARES uses its own hybrid channel IPC to notify the recipient process when data is available in an LSD field. This form of message-passing is called a "pipe," after the similar Unix concept for handling chunked or streamed buffers of text. See The ARES Operating System: Pipes for more information.
ARES Design Principles
The following tenets are generally adhered to in ARES system software, and should be used by application developers when possible for consistency.
- Use JSON and LSD for settings storage
- LSL lists are expensive and do not survive script resets. Operating ARES: Settings Storage describes how programs and users should use settings, and The ARES Operating System: The Database describes how to use the ARES
db
macros. Names and functions of significant database sections can be found earlier in this article. - Use hybrid IPC
- As described above in the ARES Message Format section, programs in ARES should only send link messages to the kernel ring or channel messages to the daemon ring. They may use
echo()
(llOwnerSay()
) for debugging. All other communications should be accomplished through the use of APIs. - Adhere to the ARES program template
- The line
#include <ARES/program>
should be at the bottom of every LSL application written for ARES. In the unlikely case the program needs event handlers, it should do so using the override macros explained inside ARES/program itself. - Avoid unnecessary script time
- Programmers should take care to implement efficient algorithms, both in terms of algorithmic complexity (big O notation, etc.) and to be aware of the speed difference between Mono calls and native LSL implementations (e.g.
llListFindList()
versus manually iterating over a list). Do not leave an ARES task running indefinitely. Do not implement high-frequency timers. Not all possible LSL scripts are suitable as ARES applications! In simulator-theoretic terms, your LSL script should be able to set itself to 'not running' whenever possible. - Avoid light bus hacks
- Companion's approach to the light bus was incomplete; many important devices depended on the
internal
command, which generated arbitrary link messages. This was a disaster for both security and compatibility. New light bus messages should be explicit and legible so that anyone listening can decipher what they mean, as well as consistent with other messages (use hyphens instead of underscores, for example). It is not a problem if the host OS needs multiple scripts to implement all functionality, as ARES eliminates crosstalk. - Avoid "junk drawer" scripts
- Companion had many modules that contained disparate features—for example, _obedience in 8.6.3 handled bolts, PIN locking and autolock, help, battery socket positioning, cable ports, local command access, and attaching the HUD; previous versions also handled gender settings. These were packed in together largely to avoid an abundance of scripts in the same link (and thus amplifying crosstalk.) ARES has no need for this design pattern, as it has room for many more scripts; dedicated scripts additionally improve flexibility for the end-user, simplify diagnosis, and provide space to grow and to implement intricate bug fixes.
- Use sensible names
- Speaking of _obedience, its name hardly suggested what it did. This was a universal program with Companion system components, which mostly had deliberately vague or poetic names to accommodate the fact that their contents regularly changed between versions. In ARES, no filename is good until you've spent ten minutes convincing yourself it is the perfect name.
- Keep "_" clean
- It may seem that NS products use preceding underscores with wild abandon in filenames, and sometimes this is true, but the ARES approach is systematic: underscores indicate files that cannot be removed without breaking some other part of the core system. They are therefore system files. Notably, breaking entries in the menus does not qualify as "breaking the system," which frees up a lot of components (like persona and vox) to be regarded as user applications. In some cases there are integrations with system components that degrade gracefully—for example, _input checks if persona is present before attempting to process a preset message.
- Keep documentation fresh
- The whole ARES Manual may be quite large and unwieldy, but the "actual" documentation (ARES Command Reference) almost never has inaccuracies or errors, and should be kept fastidiously up to date. The same applies to user programs. The moment you get something working, go update the .info file!
- exec and db are the hammer; make everything a nail
- Companion had many different mechanisms for performing instructions in sequence and loading settings from notecards. _cortex, _hierarchy, _navigator, _songbird, _emotion, _arabesque, _balance, and _xanadu-client (along with many user applications) all had the duty of either looping through lines in a text file, or otherwise telling other parts of the OS to perform several tasks in a prescribed order. This resulted in a great deal of complexity, as well as inconsistent features (e.g. Does this configuration format support comments? What do they look like?) and obscure bugs. ARES puts all its eggs into two baskets, the _exec shell script and the db database dump. These formats are explained in detail in scripting ARES and Settings Storage. To be fully useful to _exec, every program needs to implement return receipts correctly (see The ARES Operating System: Message Passing), and to have a robust command-line interface.
- Prune debug features
- The Companion kernel, _whip, contained an absurd number of alternate compilation options. New features were implemented non-destructively, by adding a new compilation option; if an experimental feature failed, it was simply "rolled back" by disabling those flags. This did not harm its performance significantly, but did make for an unreadable mess that could not have been passed on to another programmer. Likewise, debug flags often creep in that take up space in scripts and cut into performance. If the system can't run with all of its debug features turned on, those debug features need to go, or be spun off into a new program just for developer use.
- Don't bypass APIs just because of overhead
- ARES notecard loading, web file access, and input pipes are all rather slow and intrusive to implement, requiring a lot more work than their native LSL counterparts. However, they enable hibernation, which is vital for the ARES design to work. There are a few exceptions, provided they can be implemented without creating a non-hibernating program. First, package management may use native inventory notecard loading, as other forms of file storage (e.g. remote servers) cannot host packages. Second, all programs may use the cheap
io_tell()
API to avoid the burden of dealing with output pipes, but only if the target isn't a user; for messages directed at users, you must useprint()
.