/* _ ___ ______ | | | | | | | _ \ | |__| | | | | | | |_ ______ ________ | __ | | | | | | | | /| / / __ `/ ___/ _ \ | | | | |_| | |_| | |/ |/ / /_/ / / / __/ |_| |_|\____|____/|__/|__/\__,_/_/ \___/ Copyright (c) 2022, Nanite Systems Consumer Products */ /* HUDware provides an on-screen display for Nanite Systems products in an ad hoc manner with more richness than standard SL llDialog() menus. Dialogs can be directly converted into HUDware menus with minimal work if the user already has HUDware, or a small amount of boilerplate to provide the user with HUDware Lite for completely spontaneous interactions with new customers. This supersedes the previous NS Facet menu system, which was not used in any released products. */ /* ======================= PROTOCOL ======================= */ /* All communications occur on channel -82104. Messages are of the following basic format: HUD: The "HUD:" prefix protects against non-HUDware communications on the same channel. Version 1.0 of the HUDware protocol does not support modifying elements once they have been placed. Instead, destroy objects and then recreate them.*/ /* ----------------------- Aside: JSON in LSL ----------------------- */ /* A JSON value is a string containing Javascript Object Notation. LSL has built-in functions for parsing JSON without using any libraries. It is ideal for representing complex, hierarchical data, and is much more memory-efficient than LSL's native lists format. A typical JSON object looks like this when in transit: {"key1":"value1","key2":"value2","key3":["a","b","c",{"d":"e"}],"key4":"value4"} And can be spaced out for human legibility like this: { "key1": "value1", "key2": "value2", "key3": [ "a", "b", "c", { "d": "e" } ], "key4": "value4" } The above example represents an object (key-value store) with four entries: a string, a string, an array, and a string. The array contains 3 strings followed by another object. That object only has one key-value pair. An empty object is just {}, and an empty list is just []. We can hard-code JSON variables in LSL with examples like: string empty_json_object = "{}"; string empty_json_array = "[]"; string first_example = "{\"key1\":\"value1\",\"key2\":\"value2\",\"key3\":[\"a\",\"b\",\"c\",{\"d\":\"e\"}],\"key4\":\"value4\"}"; Note the backslashes in first_example. You need to escape quotation marks to write them inside LSL string literals, just like in other curly bracket languages. Relevant functions are: string value = llJsonGetValue(json_object, [key, key, ...]) // retrieve a value string updated_json = llJsonSetValue(json_object, [key, key, ...], new_value) // set a value string updated_json = llJsonSetValue(json_object, [key, key, ...], JSON_DELETE) // delete a value list keys_and_values = llJson2List(json_object); // convert a JSON object {"a":"b"} into an LSL list ["a","b"] list values_only = llJson2List(json_array); // convert a JSON array ["a","b"] into an LSL list ["a","b"] string object_json = llList2Json(JSON_OBJECT, keys_and_values); // create a JSON object {"a":"b"} from an LSL list ["a","b"] string array_json = llList2Json(JSON_ARRAY, values_only); // create a JSON array ["a","b"] from an LSL list ["a","b"] Note that all of the JSON functions above have a return value! Like with most list functions, strings are treated as non-mutable in LSL. You might be a little bemused by the [key, key, ...] part of the syntax shown above. This is a normal LSL list, holding the array/object indices (subscripts) to use when retrieving values. Using the first example object again, to get the value "e": - Javascript: var my_value = first_example["key3"][3]["d"]; - LSL: string my_value = llJsonGetValue(first_example, ["key3", 3, "d"]); Similarly, replacing the "e" with "f" in JS would be: - Javascript: first_example["key3"][3]["d"] = "f"; - LSL: first_example = llJsonSetValue(first_example, ["key3", 3, "d"], "f"); To delete a JSON value in LSL, you assign it the special value JSON_DELETE. To append an item to a JSON list: - Javascript: first_example["key3"].push("g"); - LSL: first_example = llJsonSetValue(first_example, ["key3", JSON_APPEND], "g"); (There are no other ways to manipulate JSON lists in LSL--use llJson2List() if you need to do something elaborate.) Finally, the JSON functions in LSL are designed to support seamless datatype conversion, unlike normal LSL functions. If you assign the string "3" to a value, it will be rendered into the JSON as an integer, without quotation marks. Likewise, if your string is flanked with {} or [], it will be evaluated as a JSON entity and injected directly. To avoid this behavior, use lots of escaped quotation marks (\") and other escaped punctuation. */ /* ----------------------------------- Other formatting conventions used ----------------------------------- */ /* Space-separated strings: The main strength of using JSON in LSL is in transporting and storing complex, structured data in a memory-efficient manner. LSL's native list objects may add dozens of extra bytes of overhead when adding additional elements, especially the notoriously inefficient key datatype. However, there are some cases where the speed and memory efficiency of token-based list parsing (as used in CSV files) is more practical. As a result, most of the messages introduced in HUDware 1.0 actually pass many parameters as a single space-separated blob. To parse a space-separated string: */ string example = "r g b 1 2 3"; list values = llParseStringKeepNulls(example, [" "], []); string v = llList2String(values, 2); // v now contains "b" // Grognards may be tempted to use a combination of llGetSubString() and llSubStringIndex(). // Doing so wastes a huge amount of processing time, especially in the top level of message-parsing functions, and is a bad idea. /* Colors: LSL's native color format is a floating-point RGB vector , with a range of [0.0-1.0] for each value. HUDware 1.0 only allows setting colors on text elements, for which you will see colors in the samples below represented as "r g b". Use the values directly from the vector; for example, with a vector <1, 0.5, 0> (corresponding to hex #ff7f00 or 8-bit RGB triplet 255, 127.5, 0), you should express this as "1 0.5 0". Alpha values are also expressed as floating point numbers from 0 to 1. To convert between various RGB color formats: */ // parse a hexadecimal value into an eight-bit vector: string hex = "#ff00ff"; // magenta integer r_eight = (integer)("0x" + llGetSubString(hex, 1, 2)); integer g_eight = (integer)("0x" + llGetSubString(hex, 3, 4)); integer b_eight = (integer)("0x" + llGetSubString(hex, 5, 6)); vector eight_bit = ; // (LSL can cast hex strings to integers when they're written as C hex literals (start with "0x")) // convert an 8-bit color triplet into a well-formatted floating-point LSL color vector: vector eight_bit = <255, 127, 0>; // orange vector corrected = eight_bit / 255.0; // convert a well-formatted floating-point LSL color vector into a space-separated string: vector original = <1, 0.5, 0>; // orange string space_separated = (string)original.x + " " + (string)original.y + " " + (string)original.z; // space_separated now contains "1.0000 0.5000 0.0000", more or less // convert a space-separated string into a floating-point LSL color vector: list parts = llParseStringKeepNulls(example, [" "], []); vector recovered = <(float)llList2String(parts, 0), (float)llList2String(parts, 1), (float)llList2String(parts, 2)>; // (we split the space-separated value into a list so it's easier to deal with, then slot each part into a new vector) /* The above two conventions are (almost) ubiquitous throughout other Nanite Systems products and standards, and will probably remain in place indefinitely unless something radically changes in how Second Life works. */ /* ----------------------- Example Application ----------------------- */ // See HUDware_example.lsl for a complete sample application. /* ----------------------- Protocol Summary ----------------------- */ /* All communications occur on channel -82104. SESSION CONTROL app <-> UI -> HUD:{"c":"ping","args":"My App Name 1.0","user":"UUID"} <- HUD:{"c":"pong","args":"HUDware 1.0 Workspace"} <- HUD:{"c":"pong","args":"HUDware 1.0 Lite} <- HUD:{"c":"available","args":"HUDware 1.0 Lite} <- HUD:{"c":"rejected","args":"version"} <- HUD:{"c":"rejected","args":"user"} <- HUD:{"c":"rejected","args":"full"} -> HUD:{"c":"end","args":""} <- HUD:{"c":"end","args":""} INTERFACE MANAGEMENT app <-> UI -> HUD:{"c":"window","args":"UUID source_width source_height sample_x sample_y sample_width sample_height x y callback"} -> HUD:{"c":"picture","args":"UUID source_width source_height sample_x sample_y sample_width sample_height x y callback parent"} -> HUD:{"c":"widget","args":"UUID sample_x sample_y sample_width sample_height x y callback parent"} -> HUD:{"c":"label","args":"parent x y r g b alpha callback","text":"HoverText"} -> HUD:{"c":"destroy","args":"#"} -> HUD:{"c","plugh","args":"parent x y r g b alpha callback","text":"PlughText"} INTERFACE RESPONSES app <-> UI <- HUD:{"c":"created","args":"callback # # # # ..."} <- HUD:{"c":"updated","args":"#"} <- HUD:{"c":"clicked","args":"# x y"} Note that "plugh" requires HUDware 1.0 Workspace or higher. HUDware 1.0 Lite does not support PlughText. */ /* ----------------------- Message Details ----------------------- */ /* SESSION CONTROL ping Direction: From app Arguments: Freeform string describing app name, should end with a space then the version number Additional arguments: "user" contains the UUID of the user connecting. This is required when rezzing HUDware Lite. Example: HUD:{"c":"ping","args":"My App 1.0","user":"d69ca06e-aa22-49e4-86e1-42677e26f3f5"} Protocol Version: 1.0 Description: When an avatar activates your product, e.g. by touching it, send this ping message to attempt a connection to their HUDware attachment. If no pong or rejected message is sent within 5 seconds (see below), then your product should attempt to send a HUDware Lite attachment (see next section). After this you can send one more ping to see if it connected successfully; should that second ping time out or fail, then give up. pong Direction: From HUDware Arguments: Space-separated string describing HUDware-compatible system name, API version level, and "Workspace" or "Lite" Example: HUD:{"c":"pong","args":"HUDware 1.0 Workspace"} Protocol Version: 1.0 Description: This is the ideal response to "ping" (above). You should parse the arguments string to make sure the HUD will be able to support your application needs. PlughText is not available with the Lite version of HUDware 1.0, and other messages may be added or modified in later versions. available Direction: From HUDware Arguments: Space-separated string describing HUDware-compatible system name, API version level, and "Workspace" or "Lite" Example: HUD:{"c":"available","args":"HUDware 1.0 Workspace"} Protocol Version: 1.0 Description: Sent by HUDware to announce that it is ready to receive connections. Applications can use this to detect when HUDware Lite was successfully rezzed or when a user has reconnected to SL. Typical applications should respond to "available" from their existing client with "end" so as to not disorient the user. rejected Direction: From HUDware Arguments: "version", "user", or "full" Example: HUD:{"c":"rejected","args":"full"} Protocol Version: 1.0 Description: This may be sent by the HUD in response to "ping" if there is some reason your application cannot be displayed by it. "version" means the HUD knows your app won't work because of compatibility problems; "user" means the avatar chose to reject or blacklist your application, and "full" means HUDware is already busy with one or more other applications. HUDware Lite does not allow multiple simultaneous applications. If your application does not like the version of HUDware it finds, send a message or llDialog() directly to the avatar to apprise the user of this fact. end Direction: Both Arguments: none Example: HUD:{"c","end","args":""} Protocol Version: 1.0 Description: Free all resources used by the interaction and end the session. HUDware will send this when the avatar chooses to stop using the app; you may send it from your product for any reason you choose, such as preemption by another user, or the natural conclusion of a very brief interaction such as a single-click navigation menu. INTERFACE MANAGEMENT window Direction: From app Arguments: Space-separated string containing the UUID of the image to show, the width and height of the source image, the x and y coordinates on the image to sample from, the width and height of the area to sample, the x and y coordinates at which to place the image, and a unique callback string, such as the title of the element being created. All dimensions should be in pixels. The callback string may not contain spaces. Example: HUD:{"c":"window","args":"b15a46f4-6d46-03ac-83d1-8a7bdbb19fee 1024 1024 0 128 384 128 200 200 maxwell"} Protocol Version: 1.0 Description: Creates a new window on the screen, using a subsection of the provided image, as defined by the description of the arguments, above. After this message is sent, HUDware will reply with a "created" message that translates the callback string (e.g. "maxwell") into an element number (e.g. 5). picture Direction: From app Arguments: Space-separated string containing the UUID of the image to show, width and height of the source image, x and y coordinates on the image to sample from, width and height of the area to sample, x and y coordinates at which to place the image on the parent window, a unique callback string, and the element index (handle) of a parent element. Example: HUD:{"c":"picture","args":"51703938-3fe3-cffa-19be-7ec9541b5390 512 256 64 0 32 32 300 300 icon 2"} Protocol Version: 1.0 Description: Creates a picture element and places it on an existing window element. As with the creation of a window, the image is sampled and positioned using eight coordinates. However, the last two (the position) are relative to the placement of the parent element. You must obtain the parent element's handle by reading the "created" message (see "Interface Responses" section) sent by HUDware. The "picture" message will also generate a "created" message. widget Direction: From app Arguments: Mostly the same as the "picture" message (above). However, the source image is assumed to be 1024x1024 (so skip these numbers), and the sample coordinates must be within the top half. Example: HUD:{"c":"widget","args":"a75a2186-f146-3ce4-21aa-2008676a162c 0 0 1024 512 128 128 ability 2"} Protocol Version: 1.0 Description: Creates a clickable element and places it on an existing window element. When clicked, the "clicked" message (see "Interface Responses" section) will be generated. The image is sampled and positioned using the same eight-coordinate system as before, but the source image is assumed to be 1024x1024. The top half of the source image should contain the normal appearance of each element, with the bottom half containing an alternative appearance to show while the element is being clicked. HUDware will automatically animate this visual effect. This message will automatically generate a "created" message in response to its creation. You must have the element handle from this "created" message to correctly parse "clicked" messages on the button. label Direction: From app Arguments: A space-separated list containing the index of a parent element, the x and y coordinates at which to render the text, the color (r, g, b) and opacity (alpha) to use for the text, and a callback string. See "Other formatting conventions used" section earlier in this document for information on color. Additional arguments: A separate JSON field, "text", contains the actual message to show. Example: HUD:{"c":"label","args":"parent x y r g b a callback","text":"This is some HoverText!\n \nBehold!"} Protocol Version: 1.0 Description: Creates a text message using standard SL HoverText. HoverText is always centered horizontally, and generated with its bottom pixel at the specified coordinate. Unless the color is black, it will include a one pixel drop shadow. The font and size are user-adjustable. HUDware will send a "created" message (see "Interface Responses" section) in reply to creating a label object. HoverText can display multiple lines separated with "\n", but will not display blank lines; put a non-breaking space (Alt-0160 on Windows, U+00A0 in Unicode) between line breaks for best results. destroy Direction: From app Arguments: A list of space-separated numbers, indicating HUDware elements to destroy. Example: HUD:{"c":"destroy","args":"2 3 4"} Protocol Version: 1.0 Description: Hides and frees the indicated HUDware elements. The numbers to provide can be obtained from the various interface response messages (below), especially "created". If you destroy a window, its children will also be destroyed. plugh Direction: From app Arguments: Same as "label" message (above). Additional arguments: A separate JSON field, "text", contains the actual message to show. Example: HUD:{"c","plugh","args":"4 128 128 1 0.5 1 0.8 ptext","text":"Example of PlughText"} Protocol Version: 1.0 (Workspace ONLY) Description: Generates a text message using the PlughText API. This is a double-density text format that renders monospaced characters on 8x1 face strip prims. HUDware 1.0 Workspace can display up to 127 prims (2032 characters) of PlughText simultaneously. PlughText is useful when you need exact placement of text messages in a left-aligned format, as each prim has a consistent size, unlike HoverText. However it is time-consuming to modify once placed, and can only display ASCII strings in a single pre-programmed pixel font at a fairly small size. Unlike other interface elements, the special "plughed" response is generated (see "Interface Responses" section) which includes multiple elements if needed. INTERFACE RESPONSES created Direction: From HUDware Arguments: A space-separated list containing the callback string and whatever handles (indices) of the HUDware elements that it has been assigned. Example: HUD:{"c":"created","args":"maxwell 2"} Protocol Version: 1.0 Description: Every time an element is created, HUDware will respond to your application with this "created" message. To detect clicks or destroy elements, you will need the number that was provided during element creation, called a handle. Your application should keep a list of useful handles for quick access, such as the handles assigned to buttons and windows. The Workspace-exclusive "plugh" message will produce a "created" message with multiple indices if the text provided does not fit on one element. updated Direction: From HUDware Arguments: The element or elements updated. Example: HUD:{"c":"updated","args":"3"} Protocol Version: --- Description: Future versions of HUDware will return this message in response to an "update" action. Version 1.0 of the HUDware protocol does not support modifying elements once they have been placed. Instead, destroy objects and then recreate them. clicked Direction: From HUDware Arguments: The handle of the widget element clicked, and the x and y coordinates where it was clicked. Example: HUD:{"c":"clicked","args":"4 62 120"} Protocol Version: 1.0 Description: Every time a widget element is created, HUDware will send your application this message so you can respond appropriately. To determine the correct handle, make note of the "created" message generated when the widget was created. */ /* ======================= HUDware Lite ======================= */ /* Included with the HUDware SDK is a "backup" copy of HUDware, called HUDware Lite. Since not everyone wants to carry around an extra 256-prim linkset HUD at all times, you can rez this object in response to ping timeouts, which will allow users to enjoy most of the perks of HUDware without the overhead. HUDware Lite does not include the extra elements needed for PlughText support, but is otherwise fully functional. By leaving out these prims, it is much quicker to rez and less likely you'll run out of land impact allocation on a smaller parcel. */