Difference between revisions of "LlLinksetDataWrite"

From Second Life Wiki
Jump to navigation Jump to search
m (Added caveat on out-of-memory crash risk for large values, added note on speed of function)
m (Expanded useful snippet to include unlink monitoring)
Line 40: Line 40:
** Rewriting an existing value to a '''name:value''' pair returns LINKSETDATA_NOUPDATE.
** Rewriting an existing value to a '''name:value''' pair returns LINKSETDATA_NOUPDATE.
** Writing an empty string to a nonexistent name in the datastore returns LINKSETDATA_NOTFOUND.
** Writing an empty string to a nonexistent name in the datastore returns LINKSETDATA_NOTFOUND.
* There is currently no way to write to or read from a linkset datastore from another linkset.
* The datastore is accessible from the entire linkset but acts as a property of the root prim alone. Therefore, linking and unlinking prims produces the following results:
* The datastore is accessible from the entire linkset but acts as a property of the root prim alone. Therefore, linking and unlinking prims produces the following results:
** When linking one linkset to another linkset, the combined linkset datastore includes all pairs from both datastores.
** When linking one linkset to another linkset, the combined linkset datastore includes all pairs from both datastores.
Line 45: Line 46:
*** If the combined linkset datastore would exceed 65536 bytes, pairs from the newly linked prim(s) will be added to the combined linkset datastore up to the limit. It is not currently clear in what order pairs are added, so there is no way to predict which will be dropped.
*** If the combined linkset datastore would exceed 65536 bytes, pairs from the newly linked prim(s) will be added to the combined linkset datastore up to the limit. It is not currently clear in what order pairs are added, so there is no way to predict which will be dropped.
** When unlinking a child prim from a linkset, the datastore remains in the original linkset and the child prim (now its own root prim) has an empty datastore.
** When unlinking a child prim from a linkset, the datastore remains in the original linkset and the child prim (now its own root prim) has an empty datastore.
*** If the datastore is too large to cache in a script to be rewritten after unlinking, you may need to devise a custom method of porting the datastore from the original linkset to the child prim, if necessary.
** When unlinking a root prim from a linkset, the datastore remains in the newly-unlinked root prim and the remaining prims in the original linkset have an empty datastore.
** When unlinking a root prim from a linkset, the datastore remains in the newly-unlinked root prim and the remaining prims in the original linkset have an empty datastore.
* There is no limit on the size of {{LSLP|value}} aside from the total datastore limit, so care should be taken when writing and reading very large values that could crash other scripts via [[linkset_data]]. If this is a possible risk, consider using '''llLinksetDataWriteProtected''', which does not send {{LSLP|value}} in [[linkset_data]], with a static {{LSLP|pass}}.
* There is no limit on the size of {{LSLP|value}} aside from the total datastore limit, so care should be taken when writing and reading very large values that could crash other scripts via [[linkset_data]]. If this is a possible risk, consider using '''llLinksetDataWriteProtected''', which does not send {{LSLP|value}} in [[linkset_data]], with a static {{LSLP|pass}}.
Line 67: Line 69:
// While there is no "standard" for names, it's a good idea to take some steps to prevent against other scripts accidentally overwriting your data.
// While there is no "standard" for names, it's a good idea to take some steps to prevent against other scripts accidentally overwriting your data.
// Since the datastore is shared among the entire linkset, one way to avoid name conflicts is to include the prim's UUID in the data pair's name.
// Since the datastore is shared among the entire linkset, one way to avoid name conflicts is to include the prim's UUID in the data pair's name.
// This example also monitors for llLinksetDataReset and refreshes the datastore in response, which is usually good practice if you want to keep your data!
// This example also monitors for llLinksetDataReset/unlinks and refreshes the datastore in response, which is usually good practice if you want to keep your data!


key this_uuid;
key this_uuid;
Line 106: Line 108:
             // Linkset datastore has been reset!
             // Linkset datastore has been reset!
             llLinksetDataWrite((string)llGetKey() + "-foo", lsd_data); // Write the lsd_data string into this prim UUID's pair - in this case we can't use llLinksetDataRead because the datastore is empty
             llLinksetDataWrite((string)llGetKey() + "-foo", lsd_data); // Write the lsd_data string into this prim UUID's pair - in this case we can't use llLinksetDataRead because the datastore is empty
        }
    }
    changed(integer change)
    {
        if (change & CHANGED_LINK)
        {
            // Linkset has changed!
            // Generally, this can happen for a few reasons (see Caveats above), including when an avatar sits on the object.
            // However, since the linkset data functions are very quick, it is easier to just write the data on every change to be safe for this example.
            llLinksetDataWrite((string)llGetKey() + "-foo", lsd_data); // Write the lsd_data string into this prim UUID's pair - in this case we can't use llLinksetDataRead because the datastore might be empty
         }
         }
     }
     }

Revision as of 19:17, 5 January 2023

Summary

Summary: llLinksetDataWrite, llLinksetDataWriteProtected

The llLinksetDataWrite and llLinksetDataWriteProtected functions create or update a name:value pair in the linkset datastore. The linkset datastore is a semi-permanent key-value store that is retained as a prim property regardless of whether the script is deleted or reset (via manual reset, llResetScript, llResetOtherScript, or cloning the object). If value is an empty string, the pair is deleted.

This function returns 0 on success or an error code on failure.

When these functions are called, the linkset_data event is triggered in all scripts running in the linkset with an action of LINKSETDATA_UPDATE, or LINKSETDATA_DELETE if the pair is deleted.

The linkset datastore can contain up to 65536 bytes (64 kB) of data and has no impact on script memory usage aside from the functions and events used to interact with it. Every pair written to the datastore consumes a number of bytes in the datastore equal to the length of name plus the length of value, plus an additional 32 bytes if written using llLinksetDataWriteProtected.

llLinksetDataWrite

Function: integer llLinksetDataWrite( string name, string value );

Creates or updates an unprotected name:value pair from the linkset's datastore.
Returns an integer success or failure code.

• string name The key of the name:value pair in the datastore to be updated or created.
• string value The value of the name:value pair.

llLinksetDataWriteProtected

Function: integer llLinksetDataWriteProtected( string name, string value, string pass );
0.0 Forced Delay
10.0 Energy

Creates or updates a protected name:value pair from the linkset's datastore. Further attempts to read, write or update the name:value pair must use the protected versions of those functions and must supply the same string that was used in pass.
Returns an integer success or failure code.

• string name The key of the name:value pair in the datastore to be updated or created.
• string value The value of the name:value pair.
• string pass A pass phrase used to protect the name:value pair.
Caveats
  • Protecting a name:value pair adds an additional 32 bytes to its size in the datastore regardless of the length of pass. The length of pass itself is not counted against the data storage limit.
  • When writing a protected name, the linkset_data event fires as normal, however the value parameter will be an empty string.

Constant Description
LINKSETDATA_OK 0 The name:value pair was written to the datastore.
LINKSETDATA_EMEMORY 1 A name:value pair was too large to write to the linkset datastore.
LINKSETDATA_ENOKEY 2 The name supplied to llLinksetDataWrite was empty.
LINKSETDATA_EPROTECTED 3 The name:value pair has been protected from overwrite in the linkset's datastore.
LINKSETDATA_NOTFOUND 4 The named key could not be found in the linkset's datastore when attempting to delete it.
LINKSETDATA_NOUPDATE 5 The name:value stored in the linkset was not changed by the write operation because the value stored matches the value written.

Caveats

  • Events are only fired if the linkset's datastore is changed.
    • Rewriting an existing value to a name:value pair returns LINKSETDATA_NOUPDATE.
    • Writing an empty string to a nonexistent name in the datastore returns LINKSETDATA_NOTFOUND.
  • There is currently no way to write to or read from a linkset datastore from another linkset.
  • The datastore is accessible from the entire linkset but acts as a property of the root prim alone. Therefore, linking and unlinking prims produces the following results:
    • When linking one linkset to another linkset, the combined linkset datastore includes all pairs from both datastores.
      • If any pairs have conflicting names, the combined linkset datastore will keep the pair from the original linkset and will silently drop conflicting pairs from newly added prim(s).
      • If the combined linkset datastore would exceed 65536 bytes, pairs from the newly linked prim(s) will be added to the combined linkset datastore up to the limit. It is not currently clear in what order pairs are added, so there is no way to predict which will be dropped.
    • When unlinking a child prim from a linkset, the datastore remains in the original linkset and the child prim (now its own root prim) has an empty datastore.
      • If the datastore is too large to cache in a script to be rewritten after unlinking, you may need to devise a custom method of porting the datastore from the original linkset to the child prim, if necessary.
    • When unlinking a root prim from a linkset, the datastore remains in the newly-unlinked root prim and the remaining prims in the original linkset have an empty datastore.
  • There is no limit on the size of value aside from the total datastore limit, so care should be taken when writing and reading very large values that could crash other scripts via linkset_data. If this is a possible risk, consider using llLinksetDataWriteProtected, which does not send value in linkset_data, with a static pass.

Examples

default
{
    touch_start(integer num_detected)
    {
        llLinksetDataWrite("test-name", "See you on the other side!");
        llResetScript();
    }
    state_entry()
    {
        llOwnerSay(llLinksetDataRead("test-name")); // Should print "See you on the other side!" to the owner
    }
}

Useful Snippets

Securing Against Unexpected Changes

// While there is no "standard" for names, it's a good idea to take some steps to prevent against other scripts accidentally overwriting your data.
// Since the datastore is shared among the entire linkset, one way to avoid name conflicts is to include the prim's UUID in the data pair's name.
// This example also monitors for llLinksetDataReset/unlinks and refreshes the datastore in response, which is usually good practice if you want to keep your data!

key this_uuid;
string lsd_data;

default
{
    state_entry()
    {
        this_uuid = llGetKey(); // Save the current key into this_uuid for checking later
        lsd_data = "bar"; // Store the data locally so it can be rewritten if the datastore gets erased for whatever reason
        llLinksetDataWrite((string)llGetKey() + "-foo", lsd_data); // Write the data
    }
    on_rez(integer start_param)
    {
        if (llGetKey() != this_uuid)
        {
            // Prim UUID has changed!
            // This should always be the case when on_rez is called, but is included here for clarity.
            llLinksetDataWrite((string)llGetKey() + "-foo", llLinksetDataRead((string)this_uuid + "-foo")); // Read the data from the last prim UUID's pair and write it into this prim UUID's pair
            // Note that you could also just write the lsd_data string instead of calling llLinksetDataRead, but this method is theoretically a little more robust if you expect other scripts to manipulate the data.
            llLinksetDataDelete((string)this_uuid + "-foo"); // Erase the original data to free up memory
            this_uuid = llGetKey(); // Update the UUID variable to the new UUID
        }
    }
    linkset_data(integer action, string name, string value)
    {
        if (action == LINKSETDATA_UPDATE || action == LINKSETDATA_DELETE)
        {
            if (name == (string)llGetKey() + "-foo")
            {
                // Somebody else wrote to our pair - we'll just save it in case we need it later, but you could re-write the original data instead if desired.
                lsd_data = value; // Note that in the case of LINKSETDATA_DELETE, value will be an empty string (""), which may or may not be how you want to handle that case
            }
        }
        else if (action == LINKSETDATA_RESET) 
        {
            // Linkset datastore has been reset!
            llLinksetDataWrite((string)llGetKey() + "-foo", lsd_data); // Write the lsd_data string into this prim UUID's pair - in this case we can't use llLinksetDataRead because the datastore is empty
        }
    }
    changed(integer change)
    {
        if (change & CHANGED_LINK)
        {
            // Linkset has changed!
            // Generally, this can happen for a few reasons (see Caveats above), including when an avatar sits on the object.
            // However, since the linkset data functions are very quick, it is easier to just write the data on every change to be safe for this example.
            llLinksetDataWrite((string)llGetKey() + "-foo", lsd_data); // Write the lsd_data string into this prim UUID's pair - in this case we can't use llLinksetDataRead because the datastore might be empty
        }
    }
}

Notes

  • Linkset datastore operations are synchronous and are usually processed within one server frame. Therefore, it is possible to synchronize variables between multiple scripts using the datastore alone without resorting to llMessageLinked, or to use the datastore directly as extended memory for specific workloads that need to work with extremely large datasets.

Deep Notes

Signature

function integer llLinksetDataWrite( string name, string value );
function integer llLinksetDataWriteProtected( string name, string value, string pass );