Jump to content

Tools and Technology

  • entries
    126
  • comments
    916
  • views
    286,342

Contributors to this blog

Parent/Child object Script Communications


Linden Lab

5,459 views

As part of our ongoing efforts to improve script performance, we recently made changes to how scripts are scheduled and events are delivered.  Unfortunately, we found that those changes caused some widely-used scripts to break, which led to the grid rollback last Saturday. (We were apparently unlucky in how few of those scripts were on the Release Candidate regions the previous week). We have now made further improvements that should prevent most of those problems, but even with those fixes there will be some changes in the timing and order of how scripts are run. On the whole, those changes will improve performance, but there are some scripting best practices which you should be using.  These will help you avoid being dependent on any particular ordering or timing of event delivery and script execution.

One common cause of problems is communication between objects immediately after one creates the other. When an object rezzes another object inworld using llRezObject or llRezAtRoot, the two objects frequently want to communicate, such as through calls to llRegionSayTo or llGiveInventory. The parent object receives an object_rez() event when the new object has been created, but it is never safe to assume that scripts in the new object have had a chance to run when the object_rez event is delivered. This means that the new object may not have initialized its listen() event or called llAllowInventoryDrop, so any attempt to send it messages or inventory could fail. The parent object should not begin sending messages or giving inventory from the object_rez() event, or even rely on waiting some time after that event. Instead, the parent(rezzer) and the child(rezzee) should perform a handshake to confirm that both sides are ready for any transfer. 

The sequence for this process is:

  1. The parent registers a listen event using llListen on a channel.
  2. The parent calls llRezObject and passes that channel number to the new object in the start_param.
  3. The child creates a listen event using llListen in their on_rez handler on the channel passed as the start_param.
  4. The child performs any other setup required to be ready for whatever communication it will need with the parent.
  5. The child sends a “ready” message using llRegionSayTo() on the channel to the parent.
  6. The parent transfers inventory or sends setup commands via llRegionSayTo to the child.
  7. The parent sends a “done” message to the child and may now shut down the communications channel.
  8. The child receives the “done” message and may now teardown any setup it did to enable configuration (such as calling llAllowInventoryDrop(FALSE).) 

You can find sample code for both the parent and the child below.

It's worth noting that this communication pattern has always been the best way to write your scripts. Even without the scheduler changes, the ordering of when scripts execute in the new object and when the object_rez event was delivered to the rezzer was not deterministic. It does seem to be true that making the scheduler faster has made this race condition somewhat less predictable, but making all scripts run with less scheduling overhead is worth the ordering being slightly less predictable, especially since it wasn't assured before anyway. We hope these new changes help everyone’s world run just a little smoother! To share your thoughts on this, please use this forum post.

Rezzer.lsl
////////////////////////
// Rezzer script.
//	Rez' an object from inventory, establishes a communication channel and
//  gives the rezzed object inventory.

integer COM_CHANNEL=-17974594; // chat channel used to coordinate between rezzer and rezzee
string 	REZZEE_NAME="Rezzee";

string CMD_REZZEE_READY = "REZZEE_READY";
string CMD_REZZER_DONE = "REZZER_DONE";

key 			rezzee_key;

default 
{
	//...
	touch_start(integer count)
	{
		state configure_child;
	}
	//...
}

// rez and configure a child
state configure_child
{
	state_entry()
	{
		// where to rez
		vector position = llGetPos() + <0.0, 0.0, 1.0>;	
		// establish rezzer's listen on the command channel
		llListen( COM_CHANNEL, "", "", "" );	
		// rez the object from inventory.  Note that we are passing the 
		// communication channel as the rez parameter.
		llRezObject(REZZEE_NAME, position, ZERO_VECTOR, ZERO_ROTATION, COM_CHANNEL);	
	}

	object_rez(key id)
	{	// the object has been rezzed in world.  It may not have successfully 
		// established its communication yet or done anything that it needs to 
		// in order to be ready for config. Don't do anything till we get the signal
		rezzee_key = id;
	}
	
	listen(integer channel, string name, key id, string message)
	{
		if (message == CMD_REZZEE_READY)
		{	// the rezzee has told us that they are ready to be configured.  
			// we can sanity check id == rezzee_id, but in this trivial case that is
			// not necessary.
			integer count = llGetInventoryNumber(INVENTORY_NOTECARD);
			// give all note cards in our inventory to the rezzee (we could 
			// do scripts, objects, or animations here too)
			while(count)
			{
				string name = llGetInventoryName(INVENTORY_NOTECARD, --count);
				llGiveInventory(id, name);
			}
			// And now tell the rezzee that we have finished giving it everything.
			llRegionSayTo(id, COM_CHANNEL, CMD_REZZER_DONE);
			// And we can leave configure child mode.
			state default;
		}
	}
}
          
__________________________________________________________________________________________________________________________________________

Rezzee.lsl
// Rezzee

integer com_channel = 0;
key parent_key = NULL_KEY;

string CMD_REZZEE_READY = "REZZEE_READY";
string CMD_REZZER_DONE = "REZZER_DONE";

default 
{
	//...
	on_rez(integer start_param)
	{
		com_channel = start_param;
		state configure;
	}
	//...
}

state configure
{
	state_entry()
	{	
		// Get the key of the object that rezzed us
		list details = llGetObjectDetails( llGetKey(), [ OBJECT_REZZER_KEY ] );
		parent_key = llList2Key(details, 0);	
		
		// establish our command channel and only listen to the object that rezzed us
		llListen(com_channel, "", parent_key, "");
		// Our rezzer will be giving us inventory.
		llAllowInventoryDrop(TRUE);	
		// finally tell our rezzer that we are ready
		llRegionSayTo( parent_key, com_channel, CMD_REZZEE_READY );
	}
	
	 listen( integer channel, string name, key id, string message )
	 { // in a more complex example you could check that the id and channel 
		// match but for this example we can take it on faith.
		if (message == CMD_REZZER_DONE)
		{	// the parent has told this script that it is done we can go back to 
			// our normal state.
			state default;
		}
	 }
	 
	 state_exit()
	 {	// turn off inventory drop.  
		llAllowInventoryDrop(FALSE);	
		// We don't need to clean up the listen since that will be done automatically 
		// when we leave this state.
	 }
}

 

  • Like 6
  • Thanks 7

0 Comments


Recommended Comments

There are no comments to display.

×
×
  • Create New...