llMessageLinked vs llSay
|
|
Thanto Usitnov
Lord Byron wannabe
Join date: 4 Aug 2006
Posts: 68
|
09-04-2006 21:57
I'm currently working on a two-way transformation animation script. I currently have it setup such that the main script broadcasts a message to all child prims via llMessageLinked for every step of animation. The main script waits for all child prims to report "done" to the main script before broadcasting the next step (handled by message_linked). So, I have a few questions:
Is there a point in waiting for all of the prims to report done? Is there a point in broadcasting every step, or would the prims stay in sync if they went on their own from the same starting broadcast? For a relatively large number of prims (say ~200), is it more efficient to have them all subscribe to a channel and use llSays to broadcast, or would sending link messages to each of them individually still be more efficient? Or would the delay between llSay's take too long? Would there be any appreciable delay between the first llMessageLinked and, say, the 200th? Or is there a better way to do all of this?
|
|
Shack Dougall
self become: Object new
Join date: 9 Aug 2004
Posts: 1,028
|
09-04-2006 22:50
From: Thanto Usitnov For a relatively large number of prims (say ~200), is it more efficient to have them all subscribe to a channel and use llSays to broadcast, or would sending link messages to each of them individually still be more efficient? Or would the delay between llSay's take too long?
There's definitely no reason to use llSay. You can do a broadcast using llMessageLinked() by using the constant LINK_SET to send the link message to all the prims in the link set. There are a couple of other options, too. Refer to the wiki link above. So, if all of your prims are linked, you definitely want to stay with llMessageLinked.
_____________________
Prim Composer for 3dsMax -- complete offline builder for prims and sculpties in 3ds Max http://liferain.com/downloads/primcomposer/
Hierarchical Prim Archive (HPA) -- HPA is is a fully-documented, platform-independent specification for storing and transferring builds between Second Life-compatible platforms and tools. https://liferain.com/projects/hpa
|
|
Jopsy Pendragon
Perpetual Outsider
Join date: 15 Jan 2004
Posts: 1,906
|
09-04-2006 23:05
llMessageLinked is definitely the way to go. And for your 'done' answerbacks, make sure they're replying to the sender, not using LINK_SET or LINK_ALL_OTHERS just so your listening prims aren't wasting extra processing their sibling's chatter. Not sure what kind of transformational stuff you're doing, keep in mind that llSetLinkAlpha() and llSetLinkColor() are quite useful and reduce in the need to set up extra intra-object/inter-prim communication. I'd have to test to be sure but I *think* that linked messages queue up to a point, providing the state of the target prim has a linked_message handler. If it does, I'm sure it's possible to overflow that queue if you try hard enough.
_____________________
* The Particle Laboratory * - One of SecondLife's Oldest Learning Resources. Free particle, control and targetting scripts. Numerous in-depth visual demonstrations, and multiple sandbox areas. - Stop by and try out Jopsy's new "Porgan 1800" an advanced steampunk styled 'particle organ' and the new particle texture store!
|
|
Adriana Caligari
Registered User
Join date: 21 Apr 2005
Posts: 458
|
09-04-2006 23:27
As mentioned above there is a queue for events ( llMessageLinked being an event ) - 64 if I remember correctly - go above that and the queued event will just disappear silently. Syncing - Unless you have a structural/time critical transformation I don't see the point of wasting the time needed to process 200 replies - what happens if one doesn't day "ready" ? ( back them all out - or ignore it ) I would use a rough timer like so : transform( integer stage ) { if ( (stage == 0 ) ) { llResetTime(); llMessageLinked( LINK_ALL_OTHERS,value,"stage 0",NULL_KEY ); return; } if ( (stage == 1 ) && ( (integer)llGetTime() > 3 ) ) { llMessageLinked( LINK_ALL_OTHERS,value,"stage 1",NULL_KEY ); return; } if ( ( stage == xx ) && ( (integer)llGetTime() > yy ) ) { llMessageLInked( LINK_ALL_OTHERS,value,"stage xx",NULL_KEY ) ; return; } }
|
|
Thanto Usitnov
Lord Byron wannabe
Join date: 4 Aug 2006
Posts: 68
|
09-05-2006 14:33
I completely forgot about the constants. I mean, I must have read through that wiki page a dozen times... OK, thanks for that. But I don't understand the timer dealy. Wouldn't I have to run that transform function in a while loop in order to get the time parts to trigger properly?
Edit: it looks like I forgot to mention that this is a transformation script in that it's supposed to transform an object into something different, like a car into a robot, for example. For that reason, the animation has to be properly sync'ed, or it'll look stupid. I also noticed that the time function is in float. I certainly don't want to wait a second between steps, but how long should I wait? From what I've read, there's a 0.2 second script delay after doing a prim transformation. So should I make it like... 0.3? Is there another way to have the script wait besides doing a conditional loop that checks llGetTime()? I'm thinking that I could have it do a transformation that doesn't change anything, which forces a 0.2 second wait.
|
|
Aakanaar LaSalle
Registered User
Join date: 1 Sep 2006
Posts: 132
|
09-05-2006 14:43
if you use a while loop, your script will trudge through that while loop as quick as it can.. and may not give your child prims a chance to finish one animation before going onto the next.
If you want to keep the animation smooth, and make sure the child prims have time to complete their animations before going onto the next, then use a timer.. set it to roughly the amount of time the child prims need to complete thier animation before going onto the next.
Or, you can use the method you're currently using of having the child prims report when they are done, but from the sounds of it, you'll be tracking over 200 child prims, which can be a hassle. And as posted before, if a child prim fails, then what? reset the whole animation? give an error? ignore the child prim's failure?
|
|
Ziggy Puff
Registered User
Join date: 15 Jul 2005
Posts: 1,143
|
09-05-2006 14:58
I see lag trying to get 15 prims to move in a synchronized manner. You're almost guaranteed to see lag with 200 prims no matter what you do. And like the others said, are you taking any corrective action if a prim fails to report? If you're only using the responses for synchronization, you're probably better off estimating the time it'll take, waiting for that time, and then sending the next message. A couple more things to consider: * Like Adriana said, a scripts event queue has a finite size, and I think 64 is the right number too. So if 200 prims all try to send a link message back to the controller, you're almost certain to drop some responses. In other words, a design that might work well with a 10-prim prototype, might completely fall apart in the 200-prim final version. I hope that made sense  * Are your transformation commands relative, or absolute? If you tell a prim "Move 2m up" and then tell it "Move 3m to the left", bad things will happen if you lose any messages. You're better off using absolute coordinates - they're still in local coordinate space (i.e. relative to the root prim's position/rotation), but absolute for the child prim in the sense that they're not related to the child prim's previous position. That way, if a certain prim loses a message, it's in the wrong place for one step of the animation, but hopefully moves to the right place in the next step. if your commands are relative to the prim's previous position, once one of them loses a message, it'll always lag behind, and look wrong. Most likely your main script just counts the stages in the animation, and the child prims know what to based on what stage they're in. Same thing applies - don't make the stage 3 action be "move 2m up from the current position", or you might be in trouble if things get laggy.
|
|
Shack Dougall
self become: Object new
Join date: 9 Aug 2004
Posts: 1,028
|
09-05-2006 15:01
From: Thanto Usitnov it looks like I forgot to mention that this is a transformation script in that it's supposed to transform an object into something different, like a car into a robot, for example. One thing that you should be aware of is link distances. If your object is transforming into very different shapes, then it's possible that one of the new shapes will violate the maximum link distance. What this means in practice is that at some point, it could become unlinked. It's not an obvious problem to detect, but generally if you can link/unlink/re-link an object then it's okay. In the case of a transformer, you should at least do this with each of it's forms to be sure that they all link properly. But it's possible for even a well-formed object to become unstable. For example, I had an artificial island that linked fine, but when it was rotated, it would suddenly violate the link distance and become unstable. When this happens, it doesn't unlink right away. But it could unlink when you rez it from inventory or when the sim resets. it's especially troublesome for physical objects because the pieces will fly off in all directions if they become unlinked. Not trying to scare you, but it's something to be aware of. 
_____________________
Prim Composer for 3dsMax -- complete offline builder for prims and sculpties in 3ds Max http://liferain.com/downloads/primcomposer/
Hierarchical Prim Archive (HPA) -- HPA is is a fully-documented, platform-independent specification for storing and transferring builds between Second Life-compatible platforms and tools. https://liferain.com/projects/hpa
|
|
Thanto Usitnov
Lord Byron wannabe
Join date: 4 Aug 2006
Posts: 68
|
09-05-2006 15:37
It looks like I should be able to use a timer event to handle the stepping process instead of the link_message event. the 200 messages would probably queue up pretty quickly so that's definitely bad. And anyway, if something bad happens, it'll just sit there and wait forver, which is very bad. Using a timer of, say, 0.3 seconds would handle all of the problems just fine.
And to answer some questions: the transformations are absolute. The broadcaster tells the child prims to move to a certain step position (which is why I can use a single broadcast to all child prims), where what that step means position/rotation-wise for a prim is determined by the receiver script in each prim. I'm doing it this way so it's very easy to reverse the process. And it looks like it's going to work perfectly with the timer. hoo ray!
Oh, and as for de-linking, does anyone know the formulae for max link distance? From what I've read, it looks like it's determined by the parent prim's and the child prim's size. But I think there's a minimum distance it will always link, which is 1m or something. In that case, if I do it for small objects, like toys, it should be no problem. But I want this to be scalable... I could probably have the main script check link distances on_rez to make sure that all steps of every prim would stay linked. If I knew the formula, this wouldn't take too long for the main script to figure out.
Anyway, thanks much for everything!
Edit: I'll post the fixed up code when I get home.
|
|
Jopsy Pendragon
Perpetual Outsider
Join date: 15 Jan 2004
Posts: 1,906
|
09-05-2006 15:49
Ah, I was going to suggest trying to make a hierarchy instead of having it be a One-to-All relationship, that way you could, hopefully, cut way down on the communication for all the intermediate steps.
Something like:
Parent bosses around children 2, 10, 20, ... child 2 bosses around child 3 through 9 child 10 bosses around child 11 through 19...
And the non-bosses should already know their desired start and stop (and alternate?) states, and merely be told which they need to adapt to.
(Tricky stuff... I'm working with little manikins at the moment on a much smaller scale.)
|
|
Thanto Usitnov
Lord Byron wannabe
Join date: 4 Aug 2006
Posts: 68
|
09-05-2006 16:38
OK, here's the finished script: //notes: the 0th and last step are the first and second states, respectively.
integer way; //current direction of transformation: -1 is to first state, 1 is to second state integer steps; //total number of steps - !IMPORTANT! set this manually. integer curstep; //current step integer inprogress; //whether or not transformation is already in progress to avoid bad things.
broadcast (integer step) //sends message to all linked prim to move into position { llMessageLinked(LINK_ALL_CHILDREN, step, "", ""); //sends a transformation state message to all the child prims }
default { on_rez(integer start_param) { llSetTimerEvent(0); //turn off the timer //initialize variables way=1; //since it starts in the first state, make it transform towards the second starting off steps=8; //I'm just picking a number here. curstep=1; //first step broadcast(0); //set position to first state just in case
}
touch(integer num_detected) { if(inprogress) //if it's already going... llSay(0,"Transformation already in progress."); //make sure the user knows
if(!inprogress) //if it's not going... { inprogress=TRUE; //note that it is if(way==1) //if it's going towards the second state { llSay(0, "Transformation to second state beginning."); //tell the user it is broadcast(1); //start the process with the first step towards second state } if (way==-1) //if it's going towards the first state { llSay(0, "Transformation to first state beginning."); //tell the user it is broadcast((steps - 1)); //start the process with the first step towards first state } llSetTimerEvent(0.3); //start the timer, and thus, the stepping process } }
timer() //continues stepping process { if ((0<curstep)&&(curstep<steps)) //if it's in the middle, IE: it's not done yet... { curstep+=way; //increment step counter according to direction broadcast(curstep); //broadcast next step //llSay(9, "Current Step:" + (string)curstep); //gives step# for debugging } if ((curstep<=0)||(curstep>=steps)) //if it's done { llSetTimerEvent(0); //turn off the timer llSay(0, "Transformation complete."); //tell the user way*=-1; //toggle direction of transformation inprogress=FALSE; //turn off inprogress } } }
|
|
Jopsy Pendragon
Perpetual Outsider
Join date: 15 Jan 2004
Posts: 1,906
|
09-05-2006 17:35
Just for fun, here's a different spin on your code... I tend to be a bit more terse and cuddly (which I know drives some folks nuts, but I'm a diehard K&R C guy.  ) //notes: the 0th and last step are the first and second states, respectively. integer way; //current direction of transformation: // -1 is to first state, 1 is to second state integer steps; //total number of steps - !IMPORTANT! set this manually. integer curstep; //current step default { state_entry() { if ( way==0 ) { // for recompiles, resets and on_rez's way=1; // set transformation direction steps=8; curstep=1; // first step llMessageLinked(LINK_ALL_CHILDREN, 0, "", ""); } } touch_start(integer num_detected) { state transform; } on_rez(integer start_param) { llResetScript(); } } state transform { state_entry() { if(way==1) llSay(0, "Transformation to second state beginning."); else llSay(0, "Transformation to first state beginning."); llResetTime(); llSetTimerEvent(0.3); } timer() { //continues stepping process curstep+=way; //increment step counter according to direction llMessageLinked(LINK_ALL_CHILDREN, curstep, "", ""); // Done? Go back to default state and wait for another touch: if ((curstep<=0)||(curstep>=steps)) state default; } state_exit() { // clean up way*=-1; llSay(0, "Transformation complete."); llSetTimerEvent(0.0); } touch_start(integer num_detected) { llSay(0,"Transformation already in progress."); } } // Notes: // I try to reduce the "ifs" as much as possible, not as big a deal in // this script but I like to stick to the habit of using states. :) // // I've run into problems with just touch(), when identifying the toucher // and I always use touch_start() instead now. // // Reseting the timer clock before setting the alarm is a habit I've // gotten into just to make sure the first call to timer is consistent.
|
|
Thanto Usitnov
Lord Byron wannabe
Join date: 4 Aug 2006
Posts: 68
|
09-06-2006 11:33
Well, I understand states a lot better now. I didn't know I could do it that way. It looks like there's nothing I'd change in your code, save some commenting... I plan on releasing the whole thing as open source once it's done, so I'll give you credit for the changes. My next task is the listener scripts. I'd rather do it with a strided list than a whole bunch of if statements. However, there doesn't seem to be a method of grabbing just one element from a list as whatever type that element is. Rather, I'd have to use llList2List, then convert using llList2Vector and llList2Rot. This seems silly to me. I guess I have to deal with it, though. On reading the wiki on lists, I noticed that lists aren't arrays, more like linked lists. So then, if I want to grab two elements of a stride seperately, I could do it one of two ways: seperately grab two elements from the list, then convert link_message(integer sender_num, integer num, string str, key id) { curpos=llList2Vector(llList2List(mylist,(2 * num),(2 * num))); currot=llList2Rot(llList2List(mylist,(2 * num + 1),(2 * num + 1))); } grab the stride, then grab each element from the stride link_message(integer sender_num, integer num, string str, key id) { list stridelist=llList2List(mylist,(2 * num),(2 * num + 1)); curpos=llList2Vector(llList2List(stridelist,1,1)); currot=llList2Rot(llList2List(stridelist,2,2)); } The second method should be close to twice as fast for relatively long lists, but what do you think?
|
|
Ziggy Puff
Registered User
Join date: 15 Jul 2005
Posts: 1,143
|
09-06-2006 11:46
From: someone The second method should be close to twice as fast for relatively long lists, but what do you think? Never thought about that. I'm doing it the first way on some things with pretty long lists, and I do notice perceptible lag each time the script gets back to the "pull the next set of data" state. I'll try it the other way. I'd expect a hybrid of your 2 techniques to probably work the fastest, but this is just a guess: link_message(integer sender_num, integer num, string str, key id) { list stridelist=llList2List(mylist,(2 * num),(2 * num + 1)); curpos=(vector)llList2String(stridelist,0); currot=(rotation)llList2Rot(stridelist,1); }
Basically, once you have a 2 element list, I'm not sure the overhead of building 1-element lists out of that will be worth it, compared to pulling entries from it directly. And you probably want 0 and 1 for the indices, but I could be wrong. Also, check the Wiki for warnings about pulling rotations and vectors straight out of a list. I tend to typecast them through strings for safety.
|
|
Jopsy Pendragon
Perpetual Outsider
Join date: 15 Jan 2004
Posts: 1,906
|
09-06-2006 14:07
If your vector and rotation values are going to be static, you could bypass the whole strided aspect and just use two parallel lists:
list MyVecList = [ vec1, vec2, vec3, ... ]; list MyRotList = [ rot1, rot2, rot3, ... ];
Each list retrieval would skip over half as many list items, which, if you're doing several of these on several prims, could help reduce processing demands for your transformation.
|