Welcome to the Second Life Forums Archive

These forums are CLOSED. Please visit the new forums HERE

Timing problem getting data with llMessageLinked()

AnnMarie Otoole
Addicted scripter
Join date: 6 Jan 2007
Posts: 162
05-26-2007 01:00
I ran out of memory. My stack fell into a heap :)

In the Functions WIKI under llMessageLinked they state:-

"Also, if you tend to have memory overflow problems in a single script, you can use link messages to communicate between scripts in the same object, thus enabling separate storage "

So based on this advise, I moved all my lists out to another script with an llMessageLinked query/response to get data when needed.

But there is a problem.

If I need to get a data element from a list I send a request to the list storing script with llMessageLinked instead of just extracting it from a local list. The data comes back via the link_message and sets a variable for me.

But how do I hold up on the script that needed the data until it is valid?

Although there is no delay, you can't store the returned value in a variable until the link_message event is triggered and it won't be triggered until you finish processing the data you requested (that you don't have yet).

A catch 22.

How did they envision that I could put lists in another script and get data from them?

Copying the lists as needed is a solution but negates the memory space saving of moving them out.

Any ideas?
Pale Spectre
Registered User
Join date: 2 Sep 2005
Posts: 586
05-26-2007 02:28
LSL is an Event Driven language. So, your logic has to follow those events.

Lets say from the touch_start Event Handler you request data with messageLinked... you then have to stop processing and wait for the link_message Event.

In the link_message Event processing continues.

If the requests are complex this might involve looping through the link_message Event handler. In which case something must control the progress of that loop

CODE
touch_start(integer total_number)
{
if (control > 0) llOwnerSay("...sorry, I'm busy right now.");
else
{
control = 1
llMessageLinked(linknum, num, str, id);
// logic is redirected here
}
}

link_message(integer sender_num, integer num, string str, key id)
{
//logic continues here
if (control < 9)
{
++control
llMessageLinked(integer linknum, integer num, string str, key id);
}
else
{
control = 0
// finalise logic
}
}


And, yes, it can become complicated but the trick is to always think in terms of Events. :)
Newgate Ludd
Out of Chesse Error
Join date: 8 Apr 2005
Posts: 2,103
05-26-2007 04:03
Another method is to use a cascade system, flow the data from script to script.
_____________________
I'm back......
AnnMarie Otoole
Addicted scripter
Join date: 6 Jan 2007
Posts: 162
05-26-2007 09:46
Thanks Pale, that will work.

It would be much easier if the last instruction of my "wait_for_message" state could be

state "Goto"+(string)state_number;

where state_number is a global variable.

state Goto15 {
etc.


Instead of

if(state_number == 1) state Goto1;
if(state_number == 2) state Goto2;
if(state_number == 3) state Goto3;

It won't even work with a string variable for the state name.
Jeff Kelley
Registered User
Join date: 8 Nov 2006
Posts: 223
05-26-2007 15:20
From: AnnMarie Otoole
But how do I hold up on the script that needed the data until it is valid?
You dont hold it up. Forget sequential execution and think handlers. This script will provide persistent storage of indexed data, with dump to chat and restore from notecard (remove these methods if you don't need them to recover memory).
CODE

//////////////////////////////////////////////////////
// Persistent storage declarations
//////////////////////////////////////////////////////

integer STORE_ADD = 1001;
integer STORE_READ = 1002;
integer STORE_DUMP = 1003;
integer STORE_LOAD = 1004;
integer STORE_SIZE = 1005;
integer STORE_CLEAR = 1006;
integer STORE_READ_RETURN = 1102;
integer STORE_SIZE_RETURN = 1105;

// Calling methods. Are implemented in module.

storageAddItem(string str) {
llMessageLinked(LINK_THIS,STORE_ADD,str,NULL_KEY);
}
storageReadItem(integer idx) {
llMessageLinked(LINK_THIS,STORE_READ,(string)idx,NULL_KEY);
}
storageGetSize() {
llMessageLinked(LINK_THIS,STORE_SIZE,"",NULL_KEY);
}
storageClear(){
llMessageLinked(LINK_THIS,STORE_CLEAR,"",NULL_KEY);
}
storageDump() {
llMessageLinked(LINK_THIS,STORE_DUMP,"",NULL_KEY);
}
storageLoad() {
llMessageLinked(LINK_THIS,STORE_LOAD,"",NULL_KEY);
}

// Return methods. Must be implemented in caller.

// storageReadReturn(string str) {
// Do whatever appropriate
// }
// storageSizeReturn(integer size) {
// Do whatever appropriate
// }

//////////////////////////////////////////////////////
// Implementation
//////////////////////////////////////////////////////

list persistentStorage;

integer inList(string str) {
integer i = llGetListLength(persistentStorage);
while (i--)
if (llList2String(persistentStorage,i) == str) return TRUE;
return FALSE;
}

// Private calling methods

_storageClear() {
persistentStorage = [];
}

_storageAddItem(string str) {
if (!inList(str))
persistentStorage += [str];
}

_storageReadItem(integer idx) {
_storageReadReturn(llList2String(persistentStorage,idx));
}

_storageGetSize() {
_storageSizeReturn(llGetListLength(persistentStorage));
}

_storageDump() {
integer n = llGetListLength(persistentStorage);
llOwnerSay("----- Dump of storage follow ("
+(string)n+" items)");
integer i; for (i=0; i<n; i++)
llOwnerSay("["+(string)i+"] "+llList2String(persistentStorage,i));
llOwnerSay("----- End of storage dump ("
+(string)llGetFreeMemory()+" bytes free)");
}

// Private return methods

_storageReadReturn(string str) {
llMessageLinked(LINK_THIS,STORE_READ_RETURN,str,NULL_KEY);
}
_storageSizeReturn(integer size) {
llMessageLinked(LINK_THIS,STORE_SIZE_RETURN,(string)size,NULL_KEY);
}

// Notecard reader globals

string backupCard = "*Storage Backup";
string gReaderCard;
integer gReaderLine;
key gReaderQuery;

parseSavedLine(string data) {
integer p = llSubStringIndex(data,": ");
string name = llGetSubString(data,0,p); // Separate object name
string chat = llGetSubString(data,p+2,-1); // from chat data

p = llSubStringIndex(chat,"] ");
string itemNumb = llGetSubString(chat,0,p); // Separate item number
string itemData = llGetSubString(chat,p+2,-1); // from item data

if (p != -1) { // If item found
llOwnerSay(chat);
_storageAddItem(itemData);
}
}

// State machine

default {
state_entry() {
llOwnerSay("Free memory: "+(string)llGetFreeMemory());
state running;
}
}

state running {
link_message(integer sender, integer proc, string data, key id) {
if (proc == STORE_ADD) _storageAddItem(data);
if (proc == STORE_READ) _storageReadItem((integer)data);
if (proc == STORE_SIZE) _storageGetSize();
if (proc == STORE_DUMP) _storageDump();
if (proc == STORE_CLEAR) _storageClear();
if (proc == STORE_LOAD) state load;
}
}


state load {
state_entry() {
gReaderLine = 0;
gReaderCard = backupCard;
if (llGetInventoryType(backupCard) == INVENTORY_NOTECARD){
llOwnerSay("----- Restoration of saved storage");
gReaderQuery = llGetNotecardLine(gReaderCard, gReaderLine);
} else {
llOwnerSay("----- ERROR: notecard not found");
state running;
}
}
dataserver(key queryid, string data) {
if (queryid == gReaderQuery) {
if (data != EOF) {
parseSavedLine(data);
gReaderQuery = llGetNotecardLine(gReaderCard, ++gReaderLine);
} else {
llOwnerSay("----- End of storage restoration");
state running;
}
}
}

}

Note: some obvious improvements come to mind (else, etc). I dont' want to edit the code since i'm not in-world and can't compile.
Soen Eber
Registered User
Join date: 3 Aug 2006
Posts: 428
05-26-2007 17:04
What I'm doing with my total rewrite of the multi-user lockable door code is I'm creating and parsing messages that are structured like data packets, with address and context information. For example, if someone touches a door to gain entry, the DOOR_INTF script hasa touch hander that sends out:

AUTH REQ DOOR_INTF ACCESS [key] (where [key] is the key of the person touching the door.

My AUTH script that performs the actual lookups recognizes the message (because of the AUTH envelope address) and that it needs to look up access permissions (the REQ) for [key]. It also knows that because there is a return address (DOOR_INTF ACCESS) that it needs to send out a message with the lookup return status, so it refomats the message to:

DOOR_INTF ACCESS NULL NULL ADMIN

In this case the DOOR_INTF script recognizes it needs to handle this message and routes it to a procedure for handing the ACCESS context. Because the AUTH procedure doesnt' expect a reply its stuffed a NULL return address and context, so no reply message will be generated. Finally the code compares the access status returned (ADMIN) with the status needed to operate the door, and because ADMIN access is higher then USE status it creates another message to the DOOR_PHYS function to actually open the door (this lets different types of doors use the same access and configuration methods - swing, sliding, phantom, etc). It sends the message

DOOR_PHYS OPEN NULL NULL

the DOOR_PHYS script sees this, smartly salutes, and opens the door.

Its not something you'd use for everyday scripting, but for code that needs to be expandable with a lot of configuration options, its actually pretty lightweight -- most of the functionality is already embedded within the various llList... functions so the overhead is actually quite manageable. I can also use this to communicate between objects - just add another layer on top of the packet.
AnnMarie Otoole
Addicted scripter
Join date: 6 Jan 2007
Posts: 162
05-26-2007 22:57
Well I've re-written the program (10 hours) and I must be doing something right - I've gone from about 3 or 4 states to over 20!!!

This return from fetching the link_message using the Resume pointer is very messy but I don't know how to do it any other way because the state name has to be a literal so I couldn't use a numerical index:-

if(Resume == 1) state Enroll1;
if(Resume == 2) state Money2;
if(Resume == 3) state Checkin3;
if(Resume == 4) state Checkin4;
if(Resume == 5) state Checkin5;
if(Resume == 6) state Rollup6;
if(Resume == 7) state EastGate7;
if(Resume == 8) state NorthGate8;
if(Resume == 9) state EastGateLock9;
if(Resume == 10) state SluiceGateKey10;
if(Resume == 11) state ZipCode11;
if(Resume == 12) state Manhole12;
if(Resume == 13) state Manhole13;
if(Resume == 14) state SetPlayerLevel14;
if(Resume == 15) state CheckOff15;
if(Resume == 16) state Remove16;
if(Resume == 99) state MonitorLabyrinth;
if(Resume == 100) state Transmit;


OK now for some sleep and start debugging/testing. :(

Thanks everyone for the help. The main block was giving in and using states. Once committed it was fairly straight forward but CLUMSY. No more logical flow but jumps all over the place to the various states.