Clearing a common misconception about the event model

From OpenSimulator

(Difference between revisions)
Jump to: navigation, search
(First draft)
 
 

Latest revision as of 13:08, 14 October 2024

DRAFT

Clearing a frequent misconception about the event model

Sometimes, we see code like this :

00	string A_name,
01	key A_key;
02
03	state_entry()
04	{
05		llRezObject (A_name, .....);
06		DoThis();
07		DoThat();
08	}
09
10	object_rez(key id)
11	{
12		A_key = id;
13	}

Some hope that object_rez will be executed 'as soon as' llRezObject returns, or 'while' state_entry is running. This is false. Despite the fact that the environment (script engine, simulator, network, operating system) is all multi-tasking, your script is NOT.

object_rez cannot run 'while' state_entry runs. This is concurrency, and it is the privilege of multi-tasking (or multi-threaded) languages. In a multi-threaded language, state_entry would be suspended to run object_rez, then resumed later. Like :

05	llRezObject (A_name, .....);
12	A_key = id;
06	DoThis();
07	DoThat();

LSL being single-threaded, state_entry has to complete before your script can do anything else. Then, it will dequeue the next event from the event queue, that may be (but not necessarily) object_rez. The flow of execution is actually :

05	llRezObject (A_name, .....);
06	DoThis();
07	DoThat();
12	A_key = id;

But it works !

Yes, it works. Because you have only one operation pending. Imagine:

03	state_entry()
04	{
05		llRezObject (A_name, .....);
06		llRezObject (B_name, .....);
07		DoThis();
08		DoThat();
09	}

How do you differentiate between key for objectA and key for objectB ?

The first object_rez gives A_key and the second B_key ?

Wrong.

llRezObject is an asynchronous operation and you have no control of what happens once initiated. objectA may take more time to rez than objectB for any reason. Think about initiating two i/o operations on DiskA and DiskB in C. Which completes first ? You cannot know. Your code must use some mechanism to bind i/o initiation to i/o completion. Then, when an operation completes, it can be bound to either DiskA or DiskB.


But this is LSL, not C, and there is nothing to bind object_rez to llRezObject. Fortunately, the solution is simple : Have only one rez pending at any time. Once llRezObject is called, wait for object_rez to fire before rezing next.

00	string object_rezed;
01
02	state_entry()
03	{
04		object_rezed = GetNextObject();
05		llRezObject (object_rezed, .....); 	// Prime process
06	}
07
08	object_rez(key id)
09	{
10		// id is key for 'object_rezed'
11		object_rezed = GetNextObject();
12		if (object_rezed == "") return; 	// Stop process
13		llRezObject (object_rezed, .....); 	// Propagate process
14	}

In line 10, id is guaranteed to be key of objet 'object_rezed'. You can now associate both. There is no risk of overflowing the event queue, as would be the case with a loop on llRezObject.


What may RezNext look like ? A simple dequeue from a list, maybe :

50	list ObjectsToRez = [ "A", "B", "C", "D" ];
51
52	string GetNextObject()
53	{
54	    string next_item = llList2List (ObjectsToRez, 0,0);
55	    ObjectsToRez = llDeleteSubList (ObjectsToRez, 0,0);
56	    return next_item;
57	}


But I just want the id of the last object rezed. Why is this so complex ? I would like to write

	key A_id = llRezObject (A_name, .....);

This synchronous version would block your script for a long time. Think again about disk i/o. They are million times slower than you own code. Running your whole state_entry may take nanoseconds, while waiting the rez to complete may take millis.

During the time your script will be blocked and you will be unable to respond to another event. This is especially important in the case of communications. Imagine a synchronous dialog :

	string answer = llDialog (target, "What will we eat tonight?", 
					[ "Shushis", "Hamburger" ]);

Surely, we would love to have the answer NOW (== at line 10). It would considerably streamline our code. But our script would be blocked for seconds, which is an eternity for the CPU.

The event model breaks sequentiality for the sake of responsiveness. You throw events. Some time later, you must be ready to handle their completion. You have to warp your code around the flow of events, initiating the transaction in one place, completing it elsewhere.

10	state_entry()
11	{
12		llDialog (Do you want this or that);
13	}
14
15	listen (integer channel, .....)
16	{
17		// This may be the answer to my question
18	}
Personal tools
General
About This Wiki