Second Life: Problema de comunicación entre scripts

La semana pasada el grid se vió afectado por un problema en la ejecución de scripts, específicamente cuando un objeto rezea otro y se establece una comunicación entre ambos (parent-child). Este problema, surgido luego de la actualización habitual de martes y miércoles, debió ser revertido por Linden Lab, de manera apurada, el pasado fin de semana (viernes y sábado) tuvo que reiniciar el grid para revertir los cambios y aplicar correcciones para solucionar el problema suscitado.

Ayer, en su blog oficial, Linden Lab emitió un comunicado explicando dicho problema, el cual se transcribe (traducido) a continuación:



Comunicaciones entre scripts  principal/secundario

Como parte de nuestros esfuerzos continuos para mejorar el rendimiento de los sctripts, recientemente realizamos cambios en cómo se programan los mismos y se entregan los eventos. Desafortunadamente, encontramos que esos cambios causaron la ruptura de algunos scripts ampliamente utilizados, lo que llevó a la reversión del estado del grid el sábado pasado. (Aparentemente tuvimos mala suerte en que hubiera poca cantidad de esos scripts en las regiones Release Candidate la semana anterior). Ahora hemos realizado mejoras adicionales que deberían evitar la mayoría de esos problemas, pero incluso con esas soluciones habrá algunos cambios en el tiempo y el orden de cómo se ejecutan los scripts. En general, esos cambios mejorarán el rendimiento, pero hay algunas mejores prácticas de secuencias de comandos que el usuario debería utilizar. Esto le ayudará a evitar depender de un orden o sincronización particular de entrega de eventos y ejecución de script.

Una causa común de problemas es la comunicación entre objetos inmediatamente después de que uno crea el otro. Cuando un objeto rezea a otro objeto en el mundo usando llRezObject o llRezAtRoot, los dos objetos con frecuencia necesitan comunicarse, ya sea a través de llamadas a llRegionSayTo o llGiveInventory. El objeto principal recibe un evento object_rez() cuando se ha creado el nuevo objeto, pero nunca es seguro asumir que los scripts en el nuevo objeto han tenido la oportunidad de ejecutarse cuando se entrega el evento object_rez. Esto significa que el nuevo objeto puede no haber inicializado su evento listen() o haber llamado llAllowInventoryDrop, por lo que cualquier intento de enviarle mensajes o inventario podría fallar. El objeto principal no debe comenzar a enviar mensajes ni a hacer un inventario del evento object_rez(), ni siquiera debe esperar un tiempo después de ese evento. En cambio, el objeto padre (rezzer) y el objeto hijo (rezzee) deben realizar una comunicación previa para confirmar que ambos lados están listos para cualquier transferencia.

La secuencia para este proceso es:

El objeto padre registra un evento de escucha usando llListen en un canal.
El objeto padre llama a llRezObject y pasa ese número de canal al nuevo objeto en start_param.
El objeto hijo crea un evento de escucha usando llListen en su controlador on_rez en el canal pasado como start_param.
El objeto hijo realiza cualquier otra configuración requerida para estar listo para cualquier comunicación que necesite con el objeto padre.
El objeto hijo envía un mensaje «listo» usando llRegionSayTo() en el canal asignado al objeto padre.
El objeto padre transfiere inventario o envía comandos de configuración a través de llRegionSayTo al objeto niño.
El objeto padre envía un mensaje de «hecho» al objeto hijo y ahora puede cerrar el canal de comunicaciones.
El objeto hijo recibe el mensaje «hecho» y ahora puede desmontar cualquier configuración que haya hecho para habilitar la configuración (como llamar a llAllowInventoryDrop( FALSE ) ).

Puedes ver el código de ejemplo de comunicación entre objetos padre e hijo a continuación.

Vale la pena señalar que este patrón de comunicación siempre ha sido la mejor manera de escribir tus scripts. Incluso sin los cambios en el planificador, el orden de cuándo se ejecutan los scripts en el nuevo objeto y cuándo se entregó el evento object_rez al rezzer no fue determinista. Parece cierto que hacer que el planificador sea más rápido ha hecho que esta condición de secuencia  sea algo menos predecible, pero hacer que todos los scripts se ejecuten con menos sobrecarga de programación vale la pena que el pedido sea un poco menos predecible, especialmente porque no estaba asegurado antes de todos modos. ¡Esperamos que estos nuevos cambios ayuden al mundo de todos a funcionar un poco más tranquilo! Para compartir tus pensamientos sobre esto, utiliza esta publicación del foro (En inglés).

Rezzer.lsl
////////////////////////
// Rezzer script.
//	Rez' an object from inventory, establishes a communication channel and
//  gives the rezzed object inventory.
//  (Rezea un objeto desde su inventario (pestaña Contenido), establece un canal de comunicaciones y
//  entrega del inventario del objeto rezeado.)

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
// Objeto rezeado
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.
	 }
}


Por último, vale agregar una respuesta de Rider Linden a la consulta, en el tema del foro antes mencionado, pidiendo mas detalles sobre este tema:

No voy a entrar en detalles sobre lo que cambió más allá de decir que algunos eventos (especialmente las escuchas de chat) eran almacenadas  y distribuidas una vez en cada ejecución de script para cada script. Ahora se ponen en cola de inmediato a las secuencias de comandos suscritas, lo que elimina parte del trabajo del planificador.

La ventana entre el evento object_rez y on_rez() se ha vuelto más variable y la entrega de mensajes de chat a un canal se ha vuelto más rápida. Por lo tanto, los scripters que ya usan una comunicación previa (handshake) como se recomienda en la publicación del blog no notarán ninguna diferencia y sus scripts continuarán funcionando. Sin embargo, si los rezzers dependen de un tiempo de espera para determinar cuándo su objeto rezzeado está listo, esos scripts pueden fallar.

on_rez() en múltiples scripts debe ocurrir dentro de un marco de servidor o dos entre sí (dependiendo de la carga del script del servidor), sin embargo, el orden en que ocurren sigue siendo arbitrario.

En definitiva, quienes trabajan con scripts deberán tener en cuenta esto a la hora de crear objetos que conlleven una comunicación entre si, incluso, me animo a extenderlo a objetos que, si bien no rezean otros, si mantienen una comunicación constante con otros para actualizar información entre si. Esto porque el principio es el mismo, el establecimiento cierto de una comunicación entre ambos, si está no se ha efectuado en un momento dado (por ejemplo, al reiniciarse una región) puede producirse una falla en la comunicación que hará que el sistema completo (de los scripts en los objetos rezeados) falle. También, especialmente (y eso se ha notado en estos días) la comunicación entre los HUDs y los anexos que llevamos vestidos (cuerpos, ropa, etc.).

Por lo tanto, este aviso de Linden Lab sirve para tener al tanto a los creadores de la situación y llevarlos a revisar sus scripts y corregirlos en caso de ser necesario.

SaludOS/2

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.