LlLinksetDataWrite/ja

From Second Life Wiki
Jump to navigation Jump to search

Summary

Summary: llLinksetDataWrite, llLinksetDataWriteProtected

llLinksetDataWrite および llLinksetDataWriteProtected 関数は、linkset データストア内の name:value ペアを作成または更新します。linkset データストアは、スクリプトが削除またはリセットされても(手動リセット、llResetScriptllResetOtherScript、またはオブジェクトのクローンを介して)、プリムのプロパティとして保持される半永久的なキー値ストアです。value が空の文字列の場合、ペアは削除されます。

この関数は成功時には0を、失敗時にはエラーコードを返します。

これらの関数が呼び出されると、linkset_data イベントが linkset 内で実行されているすべてのスクリプトで LINKSETDATA_UPDATE アクションでトリガーされ、ペアが削除される場合は LINKSETDATA_DELETE アクションでトリガーされます。

linkset データストアには最大で131072バイト(128 KiB)のデータを格納でき、関数とそれとやり取りするためのイベント以外のスクリプトメモリ使用量には影響しません。データストアに書き込まれる各ペアは、name の長さと value の長さに加えて、llLinksetDataWriteProtected を使用して書き込まれる場合は追加で32バイト消費します。

llLinksetDataWrite

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

データストアから保護されていない name:value ペアを作成または更新します。
Returns an integer 成功時は0、失敗時はエラーコード。

• string name データストア内の name:value ペアのキー、更新または作成するもの。
• string value name:value ペアの value

llLinksetDataWriteProtected

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

データストアから保護された name:value ペアを作成または更新します。データストアから name:value ペアを読み取る、書き込む、または更新する場合は、これらの関数の保護バージョンを使用し、pass で指定された文字列を同じものとする必要があります。
Returns an integer 成功時は0、失敗時はエラーコード。

• string name データストア内の name:value ペアのキー、更新または作成するもの。
• string value name:value ペアの value
• string pass name:value ペアを保護するために使用されるパスフレーズ。
Caveats
  • name:value ペアを保護すると、そのサイズが pass の長さにかかわらず追加で32バイト増加します。 pass の長さ自体はデータストアの制限に含まれません。
  • 保護された名前を書き込むと、linkset_data イベントは通常通りトリガーされますが、value パラメータは空の文字列になります。

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

  • イベントは linkset データストアが変更された場合のみトリガーされます。
    • 既存の値を name:value ペアに書き換えると LINKSETDATA_NOUPDATE が返されます。
    • データストアに存在しない名前に空の文字列を書き込むと LINKSETDATA_NOTFOUND が返されます。
  • 現在、別の linkset から linkset データストアに書き込みまたは読み込む方法はありません。
  • データストアは linkset 全体からアクセスできますが、単独で root prim のプロパティとして機能します。したがって、プリムのリンクおよびアンリンクを行うと次の結果が生じます。
    • linkset を別の linkset にリンクする場合、結合された linkset データストアには両方のデータストアのすべてのペアが含まれます。
      • いくつかのペアで name が競合する場合、結合された linkset データストアは元の linkset からのペアを保持し、新しく追加されたプリムの競合するペアは無言で破棄されます。
      • 結合された linkset データストアが131072バイトを超える場合、新しく追加されたプリムからのペアが限界まで結合された linkset データストアに追加されます。現在のところ、どの順序でペアが追加されるかは不明なため、削除されるペアを予測する方法はありません。
    • linkset から子プリムをアンリンクすると、データストアは元の linkset に残り、子プリム(現在は独自の root prim)は空のデータストアを持ちます。
      • データストアがアンリンク後に書き換えるためにスクリプトにキャッシュするには、データストアが元の linkset から子プリムに移動するためのカスタムメソッドを考案する必要があるかもしれません。
    • linkset から root prim をアンリンクすると、データストアは新しくアンリンクされた root prim に残り、元の linkset の残りのプリムは空のデータストアを持ちます。
  • value のサイズにはデータストアの合計制限以外の制限はありませんので、非常に大きな値を書き込むと他のスクリプトを linkset_data 経由でクラッシュさせる可能性があるため注意が必要です。
    • スクリプトが linkset_data イベントを定義していない場合、データストアが書き込まれたときにメモリにイベントパラメータをロードしないため、スクリプトは他のスクリプトからのデータストアへの書き込みからクラッシュするべきではありません。
    • これが可能なリスクである場合は、静的な pass を使用した llLinksetDataWriteProtected を考慮してください。
  • シミュレータのロールバックを介して復元されるオブジェクト、またはログアウト時に正しくサーバーに保存されないアタッチメント内のデータストアにデータがロールバックされる可能性があります。
    • ビューアのクラッシュによりアタッチメントの状態が保存されないことがありますので、アタッチメントでこれらの関数を使用する場合はデータストアのロールバックが発生する可能性があります。

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 Tampering

// 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. In certain circumstances, this resets the datastore.
            // 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 whether or not the datastore was 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 might be empty
        }
    }
}

Simple blacklist management

integer gDialogChannel;
integer gDialogHandle;
integer gManagingBlocks;

startDialog(key person)
{
    gManagingBlocks = 0;
    gDialogHandle = llListen(gDialogChannel, "", person, "");
    llDialog(person, "\nSelect action", ["List blocks", "Add block", "Remove block"], gDialogChannel);
    llSetTimerEvent(60);
}

stopDialog()
{
    llSetTimerEvent(0);
    llListenRemove(gDialogHandle);
}

default
{

    on_rez(integer sp)
    {
        llResetScript();
    }

    state_entry()
    {
        gDialogChannel = (integer)(llFrand(-10000000)-10000000);
        llListen(PUBLIC_CHANNEL, "", NULL_KEY, "");;
    }

    timer()
    {
        stopDialog();
    }

    touch_start(integer nd)
    {
        key toucherKey = llDetectedKey(0);
        if (toucherKey == llGetOwner())
        {
            startDialog(toucherKey);
        }
    }

    listen(integer channel, string name, key id, string message)
    {

        if (llGetAgentSize(id) == ZERO_VECTOR)
        {
            return;
        }

        if (channel == gDialogChannel)
        {
            stopDialog();
            if (gManagingBlocks)
            {
                message = llStringTrim(message, STRING_TRIM);
                if ((key)message)
                {
                    if (gManagingBlocks == 1)
                    {
                        llOwnerSay("Addition request has been sent to the blacklist storage");
                        llLinksetDataWrite("blocklist:" + message, "1");
                    }
                    else
                    {
                        llOwnerSay("Removal request has been sent to the blacklist storage.");
                        llLinksetDataDelete("blocklist:" + message);
                    }
                }
                else
                {
                    llOwnerSay("The UUID '" + message + "' appears to be invalid.");
                }
                startDialog(id);
            }
            else if (message == "List blocks")
            {
                list blocks = llLinksetDataFindKeys("^blocklist:", 0, 0);
                integer listLength = llGetListLength(blocks);
                llOwnerSay("Blacklist items: " + (string)listLength);
                integer i;
                while (i < listLength)
                {
                    string record = llGetSubString(llList2String(blocks, i), 10, -1);
                    llOwnerSay("- secondlife:///app/agent/" + record + "/about" + " - " + record);
                    ++i;
                }
                blocks = [];
                startDialog(id);
            }
            else if (message == "Add block" || message == "Remove block")
            {
                string label = "add to";
                gManagingBlocks = 1;
                if (message == "Remove block")
                {
                    gManagingBlocks = 2;
                    label = "remove from";
                }
                gDialogHandle = llListen(gDialogChannel, "", id, "");
                llTextBox(id, "\nPlease specify one single avatar UUID you'd like to " + label + " the blacklist storage.", gDialogChannel);
                llSetTimerEvent(60);
            }
            return;
        }

        if (llGetListLength(llLinksetDataFindKeys("blocklist:" + (string)id, 0, 1)) > 0)
        {
            llRegionSayTo(id, 0, "You're blacklisted.");
            return;
        }

        llRegionSayTo(id, 0, "Hello there, secondlife:///app/agent/" + (string)id + "/about - your message: " + message);

    }

    linkset_data(integer action, string name, string value)
    {
        if (action == LINKSETDATA_RESET || action == LINKSETDATA_DELETE || action == LINKSETDATA_UPDATE)
        {
            llOwnerSay("Blacklist storage modified.");
        }
    }

}

Notes

  • Linkset データストアの操作は同期的であり、通常は1つのサーバーフレーム内で処理されます。したがって、llMessageLinked を使わずにデータストアだけを使用して複数のスクリプト間で変数を同期させることが可能であり、また、非常に大きなデータセットで作業する必要がある特定のワークロードに対してデータストアを直接拡張メモリとして使用することもできます。

Deep Notes

Signature

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