Using llMessageLinked to split functions off into their own scripts
|
Flugelhorn McHenry
Valved bugle
Join date: 10 Jul 2004
Posts: 34
|
08-20-2005 04:47
I experienced my first stack heap collision earlier this week. After a fraught period involving relatives weeping at my bedside and machines going "ping!" I am now on the road to recovery. As part of my rehabilitation, I am trying to work out the best way to use linked messages to split functions off into their own scripts. I haven't been able to find a nice simple example of this in the fora, so I knuckled down and came up with the pair of scripts below, which don't work. I think I can see what's happening: the Main script gets stuck in the while loop while it's waiting for lastTransactionToBeProcessed to equal thisTransactionID, so the link_message event handler never gets a look-in. I'm probably taking completely the wrong approach. Can someone tell me how it should be done please? Flugelhorn Main script:integer lastTransactionToBeProcessed = 0; string lastStringReturned = "NULL-lastStringReturned";
float randBetween(float min, float max) { return llFrand(max - min) + min; }
default { state_entry() { llOwnerSay("Main script initialised."); }
touch_start(integer total_number) { integer thisTransactionID = (integer)randBetween(10,10000); llMessageLinked(LINK_THIS, thisTransactionID, "input text", (key)"listener1"); while(lastTransactionToBeProcessed != thisTransactionID) { // Do nothing } llOwnerSay("Result is: " + lastStringReturned); } link_message(integer sender_num, integer thisTransactionID, string returnedString, key recipientName) { if((string)recipientName == "main") { lastStringReturned = returnedString; lastTransactionToBeProcessed = thisTransactionID; } } }
Listener1 script (intended to hold a function used by the Main script):default { link_message(integer sender_num, integer thisTransactionID, string inputString, key recipientName) { if((string)recipientName == "listener1") { llMessageLinked(LINK_THIS, thisTransactionID, inputString + " + listener's added text", (key)"main"); } }
}
|
Jesrad Seraph
Nonsense
Join date: 11 Dec 2004
Posts: 1,463
|
08-20-2005 06:21
Scripts can each only have ONE execution thread. This means your script never goes out of the while() loop and never gets to process the link message it receives and thus never updates the value of the variable used in the while() declaration...
You must put the code that currently sits after the while() loop inside the link_message event, if you want it to be run when you get the answer back.
_____________________
Either Man can enjoy universal freedom, or Man cannot. If it is possible then everyone can act freely if they don't stop anyone else from doing same. If it is not possible, then conflict will arise anyway so punch those that try to stop you. In conclusion the only strategy that wins in all cases is that of doing what you want against all adversity, as long as you respect that right in others.
|
Flugelhorn McHenry
Valved bugle
Join date: 10 Jul 2004
Posts: 34
|
08-20-2005 06:43
From: Jesrad Seraph Scripts can each only have ONE execution thread. This means your script never goes out of the while() loop and never gets to process the link message it receives and thus never updates the value of the variable used in the while() declaration...
You must put the code that currently sits after the while() loop inside the link_message event, if you want it to be run when you get the answer back. Yes, I said that was what I suspected was going wrong. I wanted someone to give me a nice, easy workaround to allow me have script-external functions but basically preserve the flow of my original code! If I want to call a function from several different places in a script, with different things happening to the returned value in each case, I don't see how I can practically put everything into the link_message event handler. You're saying there's no nice, easy workaround? WAAAH!
|
Foolish Frost
Grand Technomancer
Join date: 7 Mar 2005
Posts: 1,433
|
08-20-2005 07:57
After thinking on this, It occured to me: You did not say what this is supposed to do.
I understand that you want to have this transfer data between two scripts using link messages, but what will each end of it do?
Perhaps data storage in a seperate script?
More info please?
|
Flugelhorn McHenry
Valved bugle
Join date: 10 Jul 2004
Posts: 34
|
08-20-2005 08:39
From: Foolish Frost After thinking on this, It occured to me: You did not say what this is supposed to do.
I understand that you want to have this transfer data between two scripts using link messages, but what will each end of it do?
Perhaps data storage in a seperate script?
More info please? I didn't say what I wanted the scripts to do because I was looking for a generic way of taking any global function which might be used in a single script and packing it off into its own script in order to preserve memory for the main script. I wanted each "function script" to be able to return a value to the main script, which could then continue with its flow, just as it would if it had used a global function within the body of its own script. For example, at the moment I'm working on an anagram-based word game which outgrew its original single script when I tried to add extra functions. I might want, for example, to pack the anagrammatising function off into its own script to save memory. Having thought about it, I'm sure it's possible to get the program to do what I want it to do by rearranging the code a lot, and by using separate scripts for procedures (i.e. not returning a result to the caller) rather than functions. In my original post I was just hoping that it would be possible to use a more elegant function-based approach. Flugelhorn
|
Foolish Frost
Grand Technomancer
Join date: 7 Mar 2005
Posts: 1,433
|
08-20-2005 09:41
I'm not sure if this is what you want, but let me try and help here: First, define these Global variables and use them for nothing else: integer outgoingnum; //these variables are used for outgoing data. string outgoingstr; key outgoingkey;
integer incommingnum; //these are for the return data. string incommingstr; key incommingkey;
integer waitingfordata; //trigger variable to see if the replay has come back. integer datareturning; // allows main state to see that it needs to process returning data. Second, make a user defined state in you main script: state senddata {
on_rez( integer param ) { llResetScript(); }
state_entry() { waitingfordata = TRUE; llMessageLinked(***LINKNUMBER***, outgoingnum, outgoingstr, outgoingkey); llSetTimerEvent(5.0); }
link_message(integer sender_num, integer num, string str, key id) { if (waitingfordata == TRUE && sender_num == ***LINKNUMBER***) { llSetTimerEvent(0.0); incommingnum = num; incommingstr = str; incommingkey = id; waitingfordata = FALSE; datareturning = TRUE; state returntomainscript } }
timer() { llWhisper(0,"Data error. No response from slave data storage script."); state errorout } } The other script in the other prim is as follows: link_message(integer sender_num, integer num, string str, key id) { if (waitingfordata == TRUE) { num = num + 1; process the data however... str = "Return: " + str; id = ""; llMessageLinked(***LINKNUMBER***, num, str, id); //return data. } }
And finally: the way of calling it: Just add in a line like: ---------------- outgoingnum = x; outgoingstr = y; outgoingkey = z; datareturning = FALSE; state senddata;
---------------- And have in the main script state: state_entry() { if (datareturning = TRUE) { x = outgoingnum; y = outgoingstr; z = outgoingkey; any special thing you need done with the returned data. } } Any of that make sense?
|
Minsk Oud
Registered User
Join date: 12 Jul 2005
Posts: 85
|
08-20-2005 10:28
From: Flugelhorn McHenry Having thought about it, I'm sure it's possible to get the program to do what I want it to do by rearranging the code a lot, and by using separate scripts for procedures (i.e. not returning a result to the caller) rather than functions. In my original post I was just hoping that it would be possible to use a more elegant function-based approach. That's basically it -- using llMessageLinked to call things asynchronously (without waiting) is really easy, and trying to wait for a result is downright ugly. If you organize your program carefully it is usually possible to get away without having to wait for results, though often a little more complicated. The easy example is something like the clip in a gun, which needs to feed multiple rezzers at once without allowing too many bullets to be fired. The synchronous approach would have each rezzer call a function in the clip that checks for bullets and drops the count. To do it asynchronously the rezzers send a "bulletRequest" event, and for each request that does not violate the count the clip generates a "bulletResponse" that names the correct rezzer. The code winds up a little more complex, but still manageable. As part of other things, I have been playing with ways to automate the rearrangements and/or allow a single script to appear to be executing multiple threads. Compiling these to LSL is disgusting enough that it will probably have to wait for LL to finish the migration to Mono (or me to get fired from my job or otherwise have a lot more free time  )
_____________________
Ignorance is fleeting, but stupidity is forever. Ego, similar.
|
Flugelhorn McHenry
Valved bugle
Join date: 10 Jul 2004
Posts: 34
|
08-20-2005 10:47
From: Foolish Frost
Any of that make sense?
I think I've made sense of it, but I don't think it will do what I want. Every time the script returns from the senddata state to the default state, it will always return at the same point (if waitingfordata=TRUE), and the script is ignorant of the point at which the external script function was called. It doesn't tie one particular bit of returned data to the line of code in which the function was called. Also, I should have made it clear before that I want all the scripts to be in the same prim (not that that makes a lot of difference to anything apart from prim count).
|
Flugelhorn McHenry
Valved bugle
Join date: 10 Jul 2004
Posts: 34
|
08-20-2005 11:11
From: Flugelhorn McHenry Every time the script returns from the senddata state to the default state, it will always return at the same point (if waitingfordata=TRUE), and the script is ignorant of the point at which the external script function was called. Hmm... Been thinking a bit more about this. Let's say all my function calls were made from the default state of the main script. I suppose, instead of using the transactionID approach that I tried originally, I could use, say, a codePointID with each function request. Then every time the script returned to the default state after new data had been received, it could check the codePointID and use jump to skip back to the point in the code at which the function was called. Yuck! My grandma taught me never to use jumps or gotos. I could see that getting very messy.
|
Foolish Frost
Grand Technomancer
Join date: 7 Mar 2005
Posts: 1,433
|
08-20-2005 11:57
That's the reason for the snippet of code at the end of my post. You have to have it recognise on entering into the main script state that data was requested and was supplied. You then have it process the data in that if statement.
Sadly, you're probably going to have to work with a design change if you want to do this. There just is no easy way to make a distributed process for dealing with data. You have to design around the idea in LSL.
Anyone who can find a better way, please let me know. I've been dealing with this for ages now.
|
Flugelhorn McHenry
Valved bugle
Join date: 10 Jul 2004
Posts: 34
|
08-20-2005 12:18
From: Foolish Frost That's the reason for the snippet of code at the end of my post. You have to have it recognise on entering into the main script state that data was requested and was supplied. You then have it process the data in that if statement. But the snippet of code at the end of your post merely recognises whether or not data was requested. It doesn't recognise where it was requested from. To return to the point at which the data was requested (assuming that the function is used at several points in the code, which it will be), I think you also need to return, along with the data itself, a signpost value specifying the point in the code at which the function was called, and then jump to the appropriate location.
|
Foolish Frost
Grand Technomancer
Join date: 7 Mar 2005
Posts: 1,433
|
08-20-2005 12:52
From: Flugelhorn McHenry But the snippet of code at the end of your post merely recognises whether or not data was requested. It doesn't recognise where it was requested from. To return to the point at which the data was requested (assuming that the function is used at several points in the code, which it will be), I think you also need to return, along with the data itself, a signpost value specifying the point in the code at which the function was called, and then jump to the appropriate location. So use datareturning to = a number other than 0 when returning data, and have the return code look for a number and apply a jump related to it. It's a design concept, not polished code. Get your hands dirty! 
|
Flugelhorn McHenry
Valved bugle
Join date: 10 Jul 2004
Posts: 34
|
08-20-2005 14:33
From: Foolish Frost
So use datareturning to = a number other than 0 when returning data, and have the return code look for a number and apply a jump related to it.
Yes, that's what I said. Thanks for your help.
|
Flugelhorn McHenry
Valved bugle
Join date: 10 Jul 2004
Posts: 34
|
08-20-2005 14:39
From: Minsk Oud using llMessageLinked to call things asynchronously (without waiting) is really easy, and trying to wait for a result is downright ugly. If you organize your program carefully it is usually possible to get away without having to wait for results, though often a little more complicated. Thanks Minsk. I think I'll probably go along that route, though I might try the jumping method first, just to see how many problems it causes.
|