Dataserver Tutorial
|
|
Landing Normandy
Proposing 4968
Join date: 28 Nov 2005
Posts: 240
|
09-18-2006 17:22
Hi guys! I'm coming into SL Scripting from PHP and I'm finding the differences to be HUGE! I tend to use databases for information but in their absence I'm using a couple of notecards as pseudo database tables. Now I have two problems to resolve and I can't figure them out from searching the forum or Google. The available Wiki's are also confusing to me! Can someone PLEASE tell me: 1) How to actually specify and read in a particular line from a notecard or two and 2) Tell me how to do something like a CSV file in a notecard. I've seen some use a simple comma to seperate things like a URL and name of file in media player notecards, how do I do this? Does anyone know of a tutorial that actually explains how all this works? I just want to create my own media player that I can upgrade by changing values in the notecards. And I know there are plenty out there, I want to make my own, I'm stuborn like that!  Cheers Landing
|
|
Aakanaar LaSalle
Registered User
Join date: 1 Sep 2006
Posts: 132
|
09-18-2006 17:55
Ok, the first part is simple.
Somewhere in the script, when it starts up, on a timer, a command, whatever you want, to get a Line from a notecard, you need to use this command
llGetNotecardLine(string name, integer line)
The name is the name of hte notecard, and the line is which line number (zero indexed, like arrays)
This command also returns a key, which is the query ID. This you may want to store if you're using multiple dataserver requests.
Once done, you want to listen for an event called 'dataserver'
dataserver(key queryid, string data) { // ... }
It returns the queryid we got from the command, so we can make sure which of multiple dataservers we got back, and the data is contained in the string.
Also, you may want to look into llGetNumberOfNotecardLines(string name) To make sure you don't go past EOF (end of notecard)
and finally, for the CSV thing, if you have the lines in the CSV format you can pass it to llCSV2List(string data) which will return a list of items based on the CSV.
If however, you choose a different character to seperate values by, look into llParseString2List(string data, list seperators, list spacers); This one also returns a list, but is a bit more complex
Seperators is a list of items [";", ":", ".", "whatever"] that you want to seperate by It discards seperators, so a string of "this and that" with a seperator list of ["s", "d"] would end up with a list of ["thi", " an", " that"]
Spacers are kept, so in the above example of "this and that" the list would be ["thi", "s", " an", "d", " that"]
You can specify however many seperators and/or spacers as you want. to not give any of either give it an empty list [].
|
|
Landing Normandy
Proposing 4968
Join date: 28 Nov 2005
Posts: 240
|
09-19-2006 01:37
Hey, thanks for that. I shall have to give it a go later when I get chance and see if that helps, although the dataserver bit is probably only a slight improvement on what I've already read (no offence!), maybe I just don't gettit! The CSV/Seperator bit is interesting however, thanks... Without it I've had to use multiple notecards! 
|
|
Landing Normandy
Proposing 4968
Join date: 28 Nov 2005
Posts: 240
|
09-19-2006 02:51
OK, I've tried that, and I can get it to tell me how many lines there are in said notecard, and I can also get it to whisper every line to me, but I can't get it to do one or the other... dataserver(key queryid, string data) { integer NOTE_LINES = (integer)data; llWhisper(0, "Notecard " + (string)NOTE1 + " has " + data + "lines"); gQuery1 = llGetNotecardLine(NOTE1, NOTE_LINE); llWhisper(0,"Notecard '" + NOTE1 + "' line number " + (string)NOTE_LINE + " = " + data); } I reckon the stumbling block is the use of data but I don't understand LSL enough to be able to work around it. I need dataserver to retrieve information from as many as three notecards, all on the same line number, as well as telling me how many lines there are. Which brings me onto the next bit; I'm using listen to operate a GUI which seems to work fine, it will add 1 to the line number when next is clicked and take 1 off when back is clicked, but I'm telling it to use the total number of lines available which are fetched in state_entry and works OK up there, but is never passed down into the listen event and I don't know how to make it. Please help! I'm sure these are really simple problems, I just can't get into it!
|
|
Newgate Ludd
Out of Chesse Error
Join date: 8 Apr 2005
Posts: 2,103
|
09-19-2006 05:03
You've missed the point slightly. The data request can be fired off from anywhere, but you will only get a reply back via the dataserver events. You will need to store this data to be reused by the Dialog routines The following is a pretty simple Notecard Reader, It's based Hank Ramos's code I picked up someplace but with a timeout incase of failures. state readNotecard { state_entry() { lineCounter = 0; integer itemtype = llGetInventoryType(notecardName); if(INVENTORY_NOTECARD == itemtype) { dataRequestID = llGetNotecardLine(notecardName, lineCounter); llSetTimerEvent(10); } else { llOwnerSay("Error - Notecard missing!"); state Running; } }
dataserver(key queryid, string data) { //Check to make sure this is the request we are making. //Remember that when data comes back from the dataserver, //it goes to *all* scripts in your prim. //So you have to make sure this is the data you want, and //not data coming from some other script. if (dataRequestID = queryid) { llSetTimerEvent(0); //If we haven't reached the end of the file //Display the incoming data, then request the next line # if (data != EOF) { //InformUser("Line #" + (string)lineCounter + ": " + data); // I use ; as comment lines in my notecards if(llGetSubString(data, 0,0) != ";") { // process Data } // Request the next line of data lineCounter += 1; dataRequestID = llGetNotecardLine(notecardName, lineCounter); llSetTimerEvent(10); } else { llSetTimerEvent(0); state Ready; } } } timer() { // The notecard read failed so abort llSetTimerEvent(0); llSay(0,"Error reading Data.Aborting."); state Ready; } }
Depending upon exactly what you are trying to do, you could read all 3 notecards into memory, or may be just the sizes, at start up. Then display the data via the Dialog system. If you want a more complete example try this thread Multiple Animations Script requiredHope that helps
|
|
Landing Normandy
Proposing 4968
Join date: 28 Nov 2005
Posts: 240
|
09-19-2006 05:30
Thanks, I'll try that again later!
|
|
Landing Normandy
Proposing 4968
Join date: 28 Nov 2005
Posts: 240
|
09-19-2006 14:09
OK, that script just plain doesn't work for me, I don't know if I need to change something but it just doesn't work. What I have managed to do though now is to be able to get the media-player I'm building to get SOME information from the notecards. I can only get it to get the URL from one notecard though and I'm sure it's something to do with the dataserver code that is causing the problem. The honest truthis that I just don't understand how to read in from the other two cards. Here's what I have so far, or at least the parts that are relevant: string mediaURL; string mediaNAME; string mediaTIME; integer currentline = 0;
default { state_entry() { llGetNotecardLine("dvdplayer.url", currentline); llGetNotecardLine("dvdplayer.time", currentline); llGetNotecardLine("dvdplayer.name", currentline); llListen(CHANNEL, "", NULL_KEY, ""); }
dataserver(key id, string data) { mediaURL = data; } This works fine for getting the URL out and the rest of the script automates just fine, but I also need it to do the following: - Get the number of lines out of the dvdplayer.url notecard and remember it in the nolines variable (not shown in the code above)
- Read in the SAME line number from the dvdplayer.name and dvdplayer.time notecards
I know there are better ways of doing what I'm trying to do but I'm using it as a learning experience with dataserver at the same time. Any help? 
|
|
ed44 Gupte
Explorer (Retired)
Join date: 7 Oct 2005
Posts: 638
|
09-19-2006 14:11
From: Newgate Ludd if (dataRequestID = queryid)
Should that not be: if (dataRequestID == queryid)
Probably won't make a lot of difference, since queryid will allways be non 0 and hence true.
|
|
Landing Normandy
Proposing 4968
Join date: 28 Nov 2005
Posts: 240
|
09-19-2006 14:17
In my 'working' script I don't need that line anyway, I think... any ideas about what else I need to add though?
|
|
Earnest Clymer
Registered User
Join date: 20 Feb 2005
Posts: 17
|
Its all about events
09-19-2006 20:13
I might be misunderstanding you, but it looks from like what you're missing is that dataserver isnt called directly, its an event handler / a callback / an asynchronous response. So it will get invoked once for each time you call llGetNoteCardLine, once the database has been queried and your result is ready. So you need to store the ids and switch on the key id that dataserver gives you to handle the data appropriately.
Apologies if I'm coming in mid-thread and missed or misunderstood something
Earnest
|
|
Aakanaar LaSalle
Registered User
Join date: 1 Sep 2006
Posts: 132
|
09-19-2006 22:33
yea, each time you call getnotecardline or getnumberoflines or whatever, will result in a seperate dataserver event. This is why you need to save the key returned when you call it.. Later on, when you get the dataserver event, you need to check the queryID to figgure out which answer you are getting, and handle the data accordingly. This can still be done using the CSV and one single notecard, if you want. but using the example above, I would store the key values from the llGetNotecardLine in a list with what you expect.. Using what you gave above, I created this string mediaURL; string mediaNAME; string mediaTIME; integer currentline = 0; list queryids; // This will be strided as [key ID, integer Type] integer URL = 1; // Constants for comparison integer TIME = 2; integer NAME = 3;
default { state_entry() { queryids += [llGetNotecardLine("dvdplayer.url", currentline), URL]; queryids += [llGetNotecardLine("dvdplayer.time", currentline), TIME]; queryids += [llGetNotecardLine("dvdplayer.name", currentline), NAME]; // Commented out this next line cause it's not needed for this. You may have it // for some other reason though, so I left it in. // llListen(CHANNEL, "", NULL_KEY, ""); }
dataserver(key id, string data) { integer index = llListFindList(queryids, [id]); // Find our Query ID if (index == -1) { return; } // This should never happen. integer type = llList2Integer(queryids, index + 1); // Get the type queryids = llDeleteSubList(queryids, index, index + 1); // Remove the query if (type == URL) { mediaURL = data; } if (type == TIME) { mediaTIME = data; } if (type == NAME) { mediaNAME = data; } // Do whatever below.. } }
You see how I store the queryID's and what i'm asking for, so when the dataserver event returns I can see which answer I'm getting back and handle it accordingly? If all you ever expect to do is one call that will return a dataserver event, you don't need to save the key. However in this case, you need to store the key's so you can put the right answers with the right questions.
|
|
Aakanaar LaSalle
Registered User
Join date: 1 Sep 2006
Posts: 132
|
09-19-2006 22:42
Oh, and as for having to have multiple files.. are all you ever expecting is to use the first line? line 0? if you think you may want more, then you'll need that llGetNumberOfLines thing or just check that data != EOF. In either case however, you can put this in your notecard line.. Url,Name,Time then when you get the dataserver event back (use the method above to make sure you're getting the notecard line you requested.) list Media = llCVS2List(data);
MediaURL = llList2String(Media, 0); MediaNAME = llList2String(Media, 1); MediaTIME = llList2String(Media, 2);
|
|
Newgate Ludd
Out of Chesse Error
Join date: 8 Apr 2005
Posts: 2,103
|
09-20-2006 00:20
From: Landing Normandy OK, that script just plain doesn't work for me, I don't know if I need to change something but it just doesn't work. From: ed44 Gupte Should that not be: if (dataRequestID == queryid)
Probably won't make a lot of difference, since queryid will allways be non 0 and hence true. My sincere apologies, No idea what happened there , it is == in the original code!
|
|
Aakanaar LaSalle
Registered User
Join date: 1 Sep 2006
Posts: 132
|
09-20-2006 01:10
oh, and since you're new to the dataserver event, i just realized i better make a couple things clear. Someone is bound to ask.
First off, there is absolutely no guarantee that you'll get your answers back in teh same order you asked the questions. This is why it's important to check the query id, when sending multiple requests.
I've noticed sometimes the lines of text that I type don't always go through in the same order I type them. This has to do mainly with lag.
Second.. There is absolutely no guarantee that you'll get all or any of the answers back to the requests you've sent. Therefore, having a timer set somewhere to check and make sure you've gotten all of your answers back would be a good idea. If after a certain period of time you find that one of your requests is unanswered, it might not hurt to make a new request.
Or at the very least have some sort of backup plan in mind in case that happens.
|
|
Landing Normandy
Proposing 4968
Join date: 28 Nov 2005
Posts: 240
|
09-20-2006 01:10
Thanks Aakanaar, I think you may well have hit the nail on the head, I'll try that as soon as I can. As for the EOF, well I've read up on some problems with this so I was planning on finding the number of lines in the notecard(s) and then using the number so if (currentline == numberoflines) { currentline = 0; } if (currentline == -1) { currentline = numberoflines - 1; } Or something to that effect. It's simillar to how I work with PHP and getting the number of rows from a table, pretty much common practice, so I just automatically applied it here too... even though LSL seems to be much more of a headache to learn than PHP! Thanks again for your help anyway, I shall try it soon
|
|
Aakanaar LaSalle
Registered User
Join date: 1 Sep 2006
Posts: 132
|
09-20-2006 01:16
yea.. that should work.. and again if you use the llGetNumberOfLines (i'm not looking at wiki, i think that's right command.) then add something to check if the answer you got back is to the numberoflines request. as that will obviously need to be handled differently than the data returned from getnotecardline.
|
|
Eloise Pasteur
Curious Individual
Join date: 14 Jul 2004
Posts: 1,952
|
09-20-2006 01:49
From: Aakanaar LaSalle First off, there is absolutely no guarantee that you'll get your answers back in teh same order you asked the questions. This is why it's important to check the query id, when sending multiple requests.
There's one way you can force this... put the request for the next line in the dataserver event. You ask for line 0 and when you get it ask for line 1 etc. That won't do with this particular problem, but is worth bearing in mind. Also worth bearing in mind... if you have multiple scripts in the same prim with dataserver events it will trigger them all. Using the queryid to filter to make sure the script (or scripts) you want to answer do saves a lot of grief.
|
|
Landing Normandy
Proposing 4968
Join date: 28 Nov 2005
Posts: 240
|
09-20-2006 07:24
OK, I tried that idea and now it doesn't work as well as it did in one respect but better in another. It is now retrieving the URL, name and length of the files but the forward and back buttons no longer work properly. I also still can't get the number of lines out for some reason... Clunky as it is, here is that section of the code as I have it: string mediaURL; string mediaNAME; string mediaTIME; integer currentline = 0; list queryids; // This will be strided as [key ID, integer Type] integer URL = 1; // Constants for comparison integer TIME = 2; integer NAME = 3;
default { state_entry() { queryids += [llGetNotecardLine("dvdplayer.url", currentline), URL]; queryids += [llGetNotecardLine("dvdplayer.time", currentline), TIME]; queryids += [llGetNotecardLine("dvdplayer.name", currentline), NAME]; llListen(CHANNEL, "", NULL_KEY, ""); // This is here for the GUI }
//dataserver(key id, string data) //{ // mediaURL = data; // This assignment will be 'remembered', since it's a global variable //} dataserver(key id, string data) { integer index = llListFindList(queryids, [id]); // Find our Query ID if (index == -1) { return; } // This should never happen. integer type = llList2Integer(queryids, index + 1); // Get the type queryids = llDeleteSubList(queryids, index, index + 1); // Remove the query if (type == URL) { mediaURL = data; } if (type == TIME) { mediaTIME = data; } if (type == NAME) { mediaNAME = data; } }
touch_start(integer num) { MENU = MENU_ + mediaNAME; llDialog(llDetectedKey(0), MENU, MEDIA_MENU_MAIN, CHANNEL); } listen(integer channel, string name, key id, string message) { MENU = MENU_; if (llListFindList(MEDIA_MENU_MAIN + MENU_OPTIONS, [message]) != -1) // verify dialog choice { if (message == "Next >") { currentline = currentline + 1; llGetNotecardLine("dvdplayer.url", currentline); llDialog(id, MENU, MEDIA_MENU_MAIN, CHANNEL); } if (message == "< Back") { currentline = currentline - 1; llGetNotecardLine("dvdplayer.url", currentline); llDialog(id, MENU, MEDIA_MENU_MAIN, CHANNEL); } if (message == "Play") { llParcelMediaCommandList( [ PARCEL_MEDIA_COMMAND_URL, mediaURL, PARCEL_MEDIA_COMMAND_PLAY ] ); } if (message == "Pause") { llParcelMediaCommandList( [ PARCEL_MEDIA_COMMAND_PAUSE ] ); } if (message == "Stop") { llParcelMediaCommandList( [ PARCEL_MEDIA_COMMAND_URL, "", PARCEL_MEDIA_COMMAND_UNLOAD ] ); } llWhisper(0, "Track number: " + (string)currentline + " - URL: " + mediaURL + " - Name: " + mediaNAME + " - Length: " + mediaTIME); } } } I don't actually know why I have llGetNotecardLine("dvdplayer.url", currentline); in the NEXT and BACK options but they were there and they worked previously... This is the crux of what I'm working with, theoretically a very simple system, but it still doesn't work for me!
|
|
Landing Normandy
Proposing 4968
Join date: 28 Nov 2005
Posts: 240
|
09-20-2006 07:39
Oh, one other thing, it almost works if I take the queryids lines and put them in the listen() event. By nearly I mean that it will go through the notecards then, just not properly; it doesn't match up the line numbers to the information being whispered to me
|
|
Dominic Webb
Differential Engineer
Join date: 1 Feb 2006
Posts: 73
|
09-20-2006 08:44
It has been my experience that people make things horribly difficult on purpose a lot of the time, simply by not knowing better... From looking at your code, it seems that you're not very familiar with the workings of the dataserver/llGetNotecardLine stuff.. Here is an example of how to read multiple lines from a notecard (taken from one of my vendors), it parses a notecard that looks like this: From: someone Item: Shoebox, blue Item: Shoebox, red Item: Shoebox, green // Variables string CardName = "Configuration"; integer ConfLine; key ReqID; list Items; integer NumItems;
default { state_entry() { llSetText("Initializing...", <1,1,1>, 1.0); // Initialise variables ConfLine = 0; Items = []; NumItems = 0;
// Send request for first line of notecard ReqID = llGetNotecardLine(CardName, ConfLine); }
dataserver(key queryid, string data) { // Did we send this query? if(queryid == ReqID) {
// While not end of notecard if(data != EOF) { list keyval = llParseString2List(data, [": "], []);
if(llList2String(keyval, 0) == "Item") { // Vendor item Items += [llList2String(keyval, 1)]; }
// Increase to next line ++ConfLine; // .. and send request to read it. ReqID = llGetNotecardLine(CardName, ConfLine); } else { // Reached EOF of notecard, finalize NumItems = llGetListLength(Items); if(!NumItems) { // No Item: lines found in notecard, stop processing llSetText("No items configured.", <1,1,1>, 1.0); return; } else { // Switch to running state switch ready; } } } } }
state ready { // Main part goes here } So my recommendation to you would be to do something like this: default { state_entry() { // Set up for reading first notecard }
dataserver() { // Read first notecard, then switch to state two } }
state two { state_entry() { // Set up for reading second notecard }
dataserver() { // Read second notecard, then switch to next state } }
// ... repeat this as many states as you need (3 for 3 notecards, etc), switch to state ready when done
state ready { // The main portion of your script goes here, all you have to do is reference lists in memory, etc }In other words, do all your setup first, so you don't have to worry about dealing with it later. - d.
_____________________
.sig space for rent.
|
|
Landing Normandy
Proposing 4968
Join date: 28 Nov 2005
Posts: 240
|
09-20-2006 09:26
Um, OK, cheers, but the code I have has come from other people's advice because although you think I'm " not very familiar with the workings of the dataserver/llGetNotecardLine stuff" it's actually worse than that, it's that I have NO IDEA what I'm doing at all!  What I'm asking is that people will actually TELL me what is wrong and preferably tell me how to fix it, because I'm an absolute idiot when it comes to LSL full stop! It's nothing like PHP to me, which is the only scripting language I know! Introducing different states is just confusing me again, I've never done anything with states and I'd rather not have to rewrite everything again after people have been so helpful. No offence! Although I'm sure your answer would work, I'm also sure that there's a simple reason why mine isn't working at the moment too! It was working earlier when only reading in the URL from one notecard, but now we've actually got it reading in the three notecards I can only get line 0, infuriatingly enough, even though the currentline integer is working perfectly OK and is adding up fine. I also STILL need to know how to add a line or two to tell me exactly how many lines there are in the notecard, I can't do it, I'm too stupid!
|
|
Dominic Webb
Differential Engineer
Join date: 1 Feb 2006
Posts: 73
|
09-20-2006 10:01
Maybe you should start over, by explaining exactly what you are trying to accomplish.
Because, from your posts, the above isn't very obvious, other than that it has to do with media, and playing it.
Feel free to be overly specific.
This might give us a chance of helping you.
- d.
_____________________
.sig space for rent.
|
|
Landing Normandy
Proposing 4968
Join date: 28 Nov 2005
Posts: 240
|
09-20-2006 10:12
I am/have built a media player, that's it. It is menu driven and can be upgraded by replacing the notecards or adding lines to them. I know I could use a seperator in the notecards to seperate the URL, name and length (will introduce a timer eventually to auto move on to the next track) but I'm trying to learn how to use multiple notecards at this point, not how to use seperators. That's it, pretty simple concept, and it all works fine in one of two guises: a) Works perfect but only reads the URL so there's no way to know what the video is until it plays b) Works perfect but only reads line 0 on all three notecards so I can only watch the first video, which thankfully happens to be Linkin Park's Numb, which I actually like at this stage, but for how much longer?  I really don't know what else I can tell you. I don't see as there's any need to re-write it because the only things that have changed between the two guises is this bit: state_entry() { queryids += [llGetNotecardLine("dvdplayer.url", currentline), URL]; queryids += [llGetNotecardLine("dvdplayer.time", currentline), TIME]; queryids += [llGetNotecardLine("dvdplayer.name", currentline), NAME]; llListen(CHANNEL, "", NULL_KEY, ""); // This is here for the GUI }
//state_entry() // { // llGetNotecardLine("dvdplayer.url", currentline); // llListen(CHANNEL, "", NULL_KEY, ""); // }
//dataserver(key id, string data) //{ // mediaURL = data; // This assignment will be 'remembered', since it's a global variable //} dataserver(key id, string data) { integer index = llListFindList(queryids, [id]); // Find our Query ID if (index == -1) { return; } // This should never happen. integer type = llList2Integer(queryids, index + 1); // Get the type queryids = llDeleteSubList(queryids, index, index + 1); // Remove the query if (type == URL) { mediaURL = data; } if (type == TIME) { mediaTIME = data; } if (type == NAME) { mediaNAME = data; } } the dataserver and state_entry that aer commented out are the old ones that worked to get the URL only
|
|
Aakanaar LaSalle
Registered User
Join date: 1 Sep 2006
Posts: 132
|
09-20-2006 18:38
From: Landing Normandy OK, I tried that idea and now it doesn't work as well as it did in one respect but better in another.
It is now retrieving the URL, name and length of the files but the forward and back buttons no longer work properly. I also still can't get the number of lines out for some reason...
Clunky as it is, here is that section of the code as I have it:
(code edited out)
I don't actually know why I have llGetNotecardLine("dvdplayer.url", currentline); in the NEXT and BACK options but they were there and they worked previously... This is the crux of what I'm working with, theoretically a very simple system, but it still doesn't work for me! Ok.. I have an idea fo what your'e trying to do.. and since i've been helping so far (sorry for being away so long, i have a new truck.) I'll help you get this thing going. I've been gonig over that script you posted.. and is it the entire script? I notice calls to variables that arn't specified elsewhere. As for using states as someone else suggested.. it has it's benifits and it's drawbacks, but I don't think it's what you want right now. So lets go over what we want. One prim.. or one object, that when touched gives a dialog that shows the current media being played. It offers a forward button and a back button to move forward or backward through the list. Currently you're wanting to use three notecards for practice, but don't mind switching down to 1 later on. (1 would be easier for this, as it's easier to make sure you've got everything you need.) And finally, you also want a pause, play, and stop button. I'm not sure how well pausing will work, i'll hve to look. Also eventually you want to be able to step through the songs using a timer. Have i got all that correct? oh, and I script in PHP as well.. and yes it is a bit different, but not a whole lot. LSL is based more on C/C++, and PHP is based on Perl which is based on C.. so they both come from the same family tree.. sorta.. I have experience with all of these mentioned. Ok, i just looked up the parcelMediaCommand thingies and understand the pause and play stuff.. yea I can definately do this.
|
|
Landing Normandy
Proposing 4968
Join date: 28 Nov 2005
Posts: 240
|
09-20-2006 19:17
Yeah, one prim, that's it, nothing more, nothing less... just a box that looks like a DVD player, just like loads of other media players in SL.
I know we're so close, it does work to a degree, like now it reads all three notecards, which is great. If we could just figure out why it will never read another line.
Loads of times in PHP I had a similar problem and it turned out I'd spelt a variable name differently somewhere, but I'm sure I've checked all this over elsewhere and like I said, if we move the lines from state_entry() into another event it will move through the notecards, just not showing the correct URL/Name/Time for that numbered line.
So close!
|