Any SL Resident worth their salt has used a prim HUD at some point...while they get the job done, they are clunky and sometimes difficult to interact with. Chat commands, dialog menus, and the like are a poor attempt at an interface for these devices; each has its limits and downsides that have been largely ignored because they are still the most feasible option available. Enter: Shared Media (or Media-On-A-Prim....MOAP). Most people know it as that thing that lets you look at porn together on the grid. There are a lot of obvious possibilities for a web-based interface for many things - however, you'd need hosting and all that junk. That's enough to turn a lot of people away from this sort of project, but what if I told you you can host websites inside of prims themselves? It's true - you can serve HTML (and JavaScript, and technically CSS - more on that later) to MOAP from within a single LSL script. Completely standalone, with all the flexibility and power of web design at your disposal. Buttons, text fields, images, video, sound, hyperlinks, and whatnot! So much whatnot! And no hosting required! Are you excited? I know I am.
First, I'm going to mention a couple of slight drawbacks, and the way to work around them. The first is that there doesn't seem to be a way for LSL to serve content in the CSS format; the solution I came up with was to load the CSS dynamically, stored in JSON format instead of traditional CSS. A simple bit of JavaScript takes the JSON data and applies it to the document's stylesheet.
Another thing that needs mentioning is that LSL will not serve HTML-formatted content to a person who is not the script owner. This is an attempt to keep people from hosting websites inside prims, or some such. Well, I worked around it...All you have to do is serve the content as plaintext and then apply it to the innerHTML attribute of an iframe element. In the context of this article, this means that your HUD can access HTML content that is stored in external objects, dynamically, instead of being limited to the ones you have stored locally in the device. There are all sorts of ways you can apply this to produce a much better interface for objects than the typical dialog menus and whatnot.
I'm not going to make this into a full-blown tutorial - I am merely going to describe the processes, link a few resources, and (eventually) include a working example. There are a few working examples already available on the wiki, but I find my approach is slightly different, more developed, and handles a few important shortcomings.
I'll start with describing the general way Shared Media works...it is a little strange and not entirely obvious; I had to do a good bit of experimenting before I really understood what was happening. When you change the Media URL, it will connect all users in the sim with MOAP enabled to that URL. Everyone who can see it will see directed to that page. So if you were to look at the requests being made to that URL, you might see a few or even several different requests coming from loading up just one page. If the server isn't anticipating this kind of behavior, it might act a little weird. However, once the URL has been reached, the 'shared' part of Shared Media is over. You can think of it like being brought to the same starting point, then setting off on your own individual path. If you interact with that page in any way, you are the only one who sees the effects. You will not 'sync up' again with the other people viewing the content until the URL is changed again. The reason I bring this up is because it not only overcomplicates things but increases the work involved by several multitudes by having so many HTTP requests through your server. The way I avoid it is by loading up a 'Start' screen that is lightweight and doesn't have anything on it but a button to proceed with. Whoever clicks the button will then be (individually) redirected to the main content of the HUD. The kicker is, the HUD can only be seen and clicked by you! If you happen to be making a non-HUD device, you can set PRIM_MEDIA_CONTROL to PRIM_MEDIA_OWNER so that only you can click that button to proceed to the main content. This keeps everyone else on the splash page, leaving you free to browse in private. Keep in mind you have to control the page content through the page itself, and not the URL bar, or else it 'shares' that content with everyone all over again.
Perhaps I'm getting ahead of myself, though. The MOAP connects to a url which the LSL script inside the prim has already created with llRequestURL(). If you didn't know about this, prims use this function to generate an actual, working URL that can be accessed with an HTTP request. Like, through the Internets! Whoa! Imagine entire websites living in prims...but HUDs only, of course. An important fact to take notice of is that the LSL script and the MOAP are two entirely different entities, working together in one device. They cannot communicate with each other except through HTTP requests. This is why we use llRequestURL() - to create a way for them to communicate. The MOAP connects to the URL of the script, which fires the http_request() event. This event contains all the data of the request: the path info, the query string, any metadata, all that stuff. You can pick this info apart to find out exactly what your MOAP is trying to do, and respond to it accordingly. Use llHTTPResponse() to send info back to it, like a load of HTML for instance! Here's the general way I handle my requests to such a device:
myscripturl/contacts/email_addresses.html?name=john
The first part of this URL is simply the script you are connecting to. The next bit, '/contacts/john.html' will be available as path info by the LSL script. The script can then infer that it needs to find a script named 'contacts', which contains 'email_addresses.html'. The part after the question mark is the query string, which can then be handled accordingly to produce John's email address specifically. Ultimately, you can handle path and query info however you want. This is the method I use as it resembles more conventional dispatching methods. Also, since all of the strings are stored in slave scripts (to be processed by the master script), you mostly avoid one of the issues regarding space and available script memory. You might be, at first, inclined to try and stick many pages into one script; if you are building any sort of complex device, take my word for it that this is not enough space for the strings you are handling and you need to spread them out a bit more. I like to keep my scripts in the Not Running state until they are needed, since they are using a lot memory.
Keep in mind that you don't have to just serve HTML here. If you reference an external JavaScript or text file from within your HTML, for example
<script src='myscripturl/resources/my_script.js'>
the MOAP will then make another request to grab a string of text called 'my_script.js' from the LSL script 'resources', and use it as JavaScript for the page. While it does take a bit longer to load things from external sources, this is only necessary because of the memory and string limits within SL. And it's not that bad, as long as you don't overdo it. I mentioned earlier that I use JavaScript to apply CSS - instead of saving files in the CSS format, I save them as JSON Objects that can be understood by the JavaScript easily. This is something that's not really covered on the wiki - I had to come up with this method myself. This is what such a function would look like:
function loadCSS(file)
{
var css = httpGet(file);//this is a seperate user-defined function for sending an xml http request
var obj = JSON.parse(css);
var params = [];
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
var rules = obj[key];
for (var kv in rules)
params += [kv+':'+rules[kv]];
var style = document.createElement('style');
style.type = 'text/css';
document.getElementsByTagName('head')[0].appendChild(style);
style.sheet.insertRule(key+' {'+params+'}',0);
}
}
}
The script basically acquires the JSON-formatted CSS parameters, iterates through it, and inserts each item into a new CSS style object of the proper format. You can hardline your CSS directly into your HTML, but again, the purpose behind this is to allow for bigger and bigger files to be used for your device. If you have a decent dispatching system, and you can serve up HTML, Javascript, and CSS as individual files, you have a damn fine start for a LSL-based webserver. The only thing you might be wanting (other than an updated webkit that isn't five years old and broken) would be some access to PHP to do some pre-processing. No need! You can use LSL to pre-process your markup before serving it! Here's what I do: I have a list of tokens such as ${NAME} and ${AVATARS} and ${URL} that I can just include anywhere in the markup. When it runs through the pre-processor, all instances of those tokens are replaced with the appropriate info. So, a page with '${NAME}' in it would come out, in my case, as a page with 'Arkane Flux' instead. You want to go easy on the number of evaluations you do like this though, as searching through strings can get a bit heavy on resource consumption. You can use whatever method you like, in the end; this is just what I prefer. I am intentionally staying away from direct examples because this is less of a proof-of-concept and more of a summarization of the things I've had to consider and work around in my experiences creating such devices. CherryPy users might recognize the style of token I use. CherryPy and other webserver frameworks are definitely worth looking into as well, but are out of the scope of this article (we are focusing on locally hosting files). Coming back to the topic, LSL can be substituted for PHP in many cases - I would stay away from heavy lifting, though. PHP is FAST, LSL is SLOW (by comparison). Again, it's out of the scope of this article but if you need to do some big preprocessing work, there is nothing wrong with outsourcing the job to an external PHP script. I have not encountered such a need with the devices I have made, however. It is just good to be aware of your options. (While I'm writing this, I'm considering trying to serve a PHP file from LSL. I'm not sure it can be done, but if it can I will update my info accordingly. If the pre-processing can be done through the MOAP somehow, instead of the LSL backend, I believe it would be a lot faster. I haven't been held up at all by my current method, so there has been no reason to explore this option). This pre-processing is how you can send your query string parameters and other info directly into the markup, without having to use additional HTTP requests to acquire it.
So that's basically it, as far as the actual requesting and serving of files goes. There are a lot of other points and concepts I could talk about, and so I will offer a few that I think might be interesting to you as a creator of a MOAP HUD:
~There is no way to serve images, video, or audio from LSL, but you can still embed them from external sources. Keep in mind the MOAP webkit is quite outdated, and you will likely have a lot of issues making content involving audio and video. Nothing that can really be done about it (by us). You can forget about HTML5, as well.
~Know what a Data URI is? MOAP supports them. Read about it here: http://wiki.secondlife.com/wiki/Shared_Media_and_data_URIs. This is pretty cool, because you don't need a HUD to display HTML content this way, as it is not using llHTTPResponse() to serve it. The input length is limited to 1000 bytes, which can be worked around with TinyURL. It has its uses, however it is not ideal for the technique discussed in this article because of the limited content length, and also the fact that since it is entered through the Media Control bar, it is shared with everyone (instead of keeping them on the splash screen as discussed before). Now that I'm considering it, I will probably use a data:URI for the splash screen and then proceed with the regular method after that. It always helps me to talk about these things.
~Focus on making your content responsive. Remember, these ain't your daddy's SL HUDs. You've got access to font control, hover effects, GIF files, and more. Really jazz it up! The main reason to make a HUD like this is for a superior interface. Consider animated backgrounds, transition effects, and the like.
~The webkit DOES use a cache. You will notice that images load slower the first time around, and faster after that. Use responsible caching methods to increase the overall speed and performance of you device. If you don't know what a CSS sprite is, look into it.
~Eventually I will make an example HUD that combines all of these principles in a working demo. Eventually. I'm still finding new ways to improve my system (I came up with a few new ideas while writing this), so, once I feel I've really covered all the bases, I will release it. Until then, check out this one by Kelly Linden: http://wiki.secondlife.com/wiki/HTML_HUD_Demo
This about wraps up my presentation. Although these concepts are not new, people are always surprised to hear that you can do such things without external hosting. I always end up talking about it for days...now, I can simply link them to this! I hope this inspires more people to involve themselves in making the HUDs of the future!
No comments:
Post a Comment