A life twice-lived ...

Prim Persistent Memory Array

What

The Prim Persistent Memory Array (PPMA) is a persistent storage method, or “database” of sorts, for the Myriad RPG HUD in OpenSimulator/OSgrid.

How

LSL scripts have traditionally stored small bits of data, sometimes carefully formatted, into the object's Description field.

PPMA extends this concept by linking one or more small prims, each set with the default transparent texture, to the HUD prim posititioned away from the viewer.

Each prim holds a number of persistent elements which can be changed under script control, such as color, position, etc. Once set, these data elements are saved as part of the prim itself. Setting data is as simple as llSetLinkedPrimitiveParams([param,data]) calls. Reading data is as simple as llList2String(llGetLinkedPrimitiveParam([param]),0);

In a full PPMA implementation, such as the Myriad Lite HUD in OSgrid 254 prims are linked to the HUD prim, providing 254 additional “rows” of persistent data storage, with 3 “columns” per row:

Prim Element Data Type Max Length Notes
PRIM_NAME string 63
PRIM_DESC string 127
PRIM_TEXT string 254 must set a color vector and alpha float with this. Alpha 0.0 will prevent the hover text from “showing through” the HUD prim itself

Why

Persistence

LSL was designed to allow for simple behavior to be added to objects.

However, the ability to store persistent user-created data was never really designed into LSL.

Traditionally, if someone wanted a script to be able to “remember” data between sessions, there were 3 ways to do it:

  1. In memory - just don't reset the script and your script globals will generally stay… generally.
  2. In Notecard - read only since LSL doesn't allow notecard writes, and requires painful line-by-line dataserver event handling
  3. HTTP to an external web database such as Silo, SLDB, or other web server stack, which LL throttles too heavily to be useful for many situations.

With PPMA, the HUD state gets saved by the simulator itself automatically during periodic region and avatar data snapshots, and during logoff, teleport, or sim crossing events as part of the HUD prims.

Simplicity

Once the HUD object itself is made, using Myriad Lite with PPMA is far simpler than a HUD which requires an external database.

Everything for the HUD stays contained in-world, using only the traditional viewer tools for creation or management.

This makes the HUD usable even for region renters who do not have Apache/PHP/MySQL or IIS/ASP/MSSQL access or expertise with their region.

PPMA also means that IAR and OAR exports/imports from OpenSim can be used to backup and restore the HUD and rezzed objects which use it; No separate external database backup and restore is required.

Global State

The most compelling reason to me for PPMA with Myriad Lite is simply that it makes coordinating data across numerous script modules within the HUD very simple.

Normally, each script keeps its own local variables. For two or more scripts to coordinate the same data within the same prim, link messages must be sent, received, and processed. The link messages merely store the same data over and over again across each script that needs it. This means that all copies of the same data have to be kept in sync which requires careful and consistent scripting.

With PPMA, all scripts in the HUD can look in the same place for the same piece of information, without link messages and duplicated local variables in each script.

This also has the advantage of making data lookups immediate, rather than having to pause and wait for an event. All scripts can read the data stored in the linked prims directly, without having to send or wait for a link message or chat message with some other script.

For example: Traditionally, each script that needed to know your Close Combat skill rating would keep its own CLOSECOMBAT variable, and require a link message receiver or sender. Some script would have to become the “keeper” of the master copy of the close combat value, and update all other scripts when it was changed. Any error in any script on the message format would mean that script stops staying in sync with the master copy of the close combat value.

PPMA eliminates this issue by allowing all scripts to read the same data and act on it immediately. All scripts can look into the same “pool” of storage without complications.

However, for consistency, only 1 script should still WRITE to the pool. This ensures that two scripts don't clobber each other's updates to a shared value. Design wise, you can think of this as “all can read, only 1 can write” any particular data field.

Where

Current developed and test deployed in OSgrid however it should be compatible with SecondLife as well once Preview 7 is released for wider testing.

Who

Not sure who first used this in SecondLife, but PPMA is my own design and implementation for an extended “database” based on prim storage.

When

I first started using this for a talker/emoter in the Splintered Rock roleplay regions of SecondLife sometime in 2007 or 2008 by saving your RP alias as the talker/emoter object description.

Example

[07:05]  ☐ RECORD=[2] NAME=[FLAG] DESC=[DEBUG] TEXT=[0]
[07:05]  ☐ RECORD=[3] NAME=[FLAG] DESC=[INCAPACITATED] TEXT=[0]
[07:05]  ☐ RECORD=[4] NAME=[CHARACTER] DESC=[NAME] TEXT=[Alizana Reiland]
[07:05]  ☐ RECORD=[5] NAME=[ARMOR] DESC=[RATINGS] TEXT=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
[07:05]  ☐ RECORD=[6] NAME=[FLAG] DESC=[DEAD] TEXT=[0]
[07:05]  ☐ RECORD=[7] NAME=[SPECIE] DESC=[SPECIE] TEXT=[Human]
[07:05]  ☐ RECORD=[8] NAME=[FLAG] DESC=[ANIMATE] TEXT=[1]
[07:05]  ☐ RECORD=[9] NAME=[BACKGROUND] DESC=[BACKGROUND] TEXT=[Normal]
[07:05]  ☐ RECORD=[10] NAME=[CAREER] DESC=[CAREER] TEXT=[None]
[07:05]  ☐ RECORD=[11] NAME=[STATISTIC] DESC=[Power] TEXT=[3]
[07:05]  ☐ RECORD=[12] NAME=[STATISTIC] DESC=[Grace] TEXT=[3]
[07:05]  ☐ RECORD=[13] NAME=[STATISTIC] DESC=[Intellect] TEXT=[3]
[07:05]  ☐ RECORD=[14] NAME=[STATISTIC] DESC=[Spirit] TEXT=[3]
[07:05]  ☐ RECORD=[15] NAME=[RESILIENCE] DESC=[Wounds] TEXT=[3]
[07:05]  ☐ RECORD=[16] NAME=[RESILIENCE] DESC=[CurrentWounds] TEXT=[3]
[07:05]  ☐ RECORD=[17] NAME=[RESILIENCE] DESC=[CriticalWounds] TEXT=[2]
[07:05]  ☐ RECORD=[18] NAME=[PROGRESS] DESC=[XP] TEXT=[0]
[07:05]  ☐ RECORD=[19] NAME=[RESILIENCE] DESC=[CurrentCriticalWounds] TEXT=[2]
[07:05]  ☐ RECORD=[20] NAME=[PROGRESS] DESC=[XPLEVEL] TEXT=[1]
[07:05]  ☐ RECORD=[21] NAME=[RESILIENCE] DESC=[Resolve] TEXT=[3]
[07:05]  ☐ RECORD=[22] NAME=[PROGRESS] DESC=[GP] TEXT=[0]
[07:05]  ☐ RECORD=[23] NAME=[RESILIENCE] DESC=[CurrentResolve] TEXT=[3]
[07:05]  ☐ RECORD=[24] NAME=[PROGRESS] DESC=[STATPOOL] TEXT=[0]
[07:05]  ☐ RECORD=[25] NAME=[BOON] DESC=[Ally] TEXT=[1]
[07:05]  ☐ RECORD=[26] NAME=[PROGRESS] DESC=[HEALTHPOOL] TEXT=[0]
[07:05]  ☐ RECORD=[27] NAME=[FLAW] DESC=[Enemy] TEXT=[1]
[07:05]  ☐ RECORD=[28] NAME=[PROGRESS] DESC=[SKILLPOOL] TEXT=[0]
[07:05]  ☐ RECORD=[29] NAME=[SKILL] DESC=[Ranged Combat] TEXT=[1]
[07:05]  ☐ RECORD=[30] NAME=[PROGRESS] DESC=[SFXPOOL] TEXT=[0]
[07:05]  ☐ RECORD=[31] NAME=[SKILL] DESC=[Close Combat] TEXT=[1]
[07:05]  ☐ RECORD=[32] NAME=[SKILL] DESC=[Deceit] TEXT=[1]
[07:05]  ☐ RECORD=[33] NAME=[SKILL] DESC=[Persuasion] TEXT=[1]
[07:05]  ☐ RECORD=[34] NAME=[STUNT] DESC=[STUNT] TEXT=[]
[07:05]  ☐ RECORD=[35] NAME=[QUOTE] DESC=[QUOTE] TEXT=[]
[07:05]  ☐ RECORD=[36] NAME=[CHARACTER] DESC=[RP] TEXT=[0]
[07:05]  ☐ RECORD=[37] NAME=[CHARACTER] DESC=[NICKNAME] TEXT=[Allie]
[07:05]  ☐ RECORD=[38] NAME=[CHARACTER] DESC=[TITLE] TEXT=[Pilot]
[07:05]  ☐ RECORD=[39] NAME=[CHARACTER] DESC=[FACTION] TEXT=[Green Faction]
[07:05]  ☐ RECORD=[40] NAME=[CONFIG] DESC=[CHANCOMMAND] TEXT=[5]
[07:05]  ☐ RECORD=[41] NAME=[CONFIG] DESC=[CHANOOCCHAT] TEXT=[22]
[07:05]  ☐ RECORD=[42] NAME=[CONFIG] DESC=[CHANOOCEMOTE] TEXT=[23]
[07:05]  ☐ RECORD=[43] NAME=[CONFIG] DESC=[CHANICCHAT] TEXT=[44]
[07:05]  ☐ RECORD=[44] NAME=[CONFIG] DESC=[CHANICTHINK] TEXT=[45]
[07:05]  ☐ RECORD=[45] NAME=[CONFIG] DESC=[CHANICEMOTE] TEXT=[66]
[07:05]  ☐ RECORD=[46] NAME=[CONFIG] DESC=[CHANNARRATE] TEXT=[88]
[07:05]  ☐ RECORD=[47] NAME=[CONFIG] DESC=[OOCPREFIX] TEXT=[((]
[07:05]  ☐ RECORD=[48] NAME=[CONFIG] DESC=[OOCSUFFIX] TEXT=[))]
[07:05]  ☐ RECORD=[49] NAME=[CONFIG] DESC=[PHYSBULLETS] TEXT=[0]
[07:05]  ☐ RECORD=[50] NAME=[FLAG] DESC=[CONTROLS] TEXT=[1]
[07:05]  ☐ RECORD=[51] NAME=[CONFIG] DESC=[ARMLENGTH] TEXT=[1.0]
[07:05]  ☐ RECORD=[52] NAME=[FLAG] DESC=[FISTS] TEXT=[0]
[07:05]  ☐ RECORD=[53] NAME=[MYRIAD] DESC=[MINSTAT] TEXT=[1]
[07:05]  ☐ RECORD=[54] NAME=[CONFIG] DESC=[LEGLENGTH] TEXT=[1.5]
[07:05]  ☐ RECORD=[55] NAME=[CONFIG] DESC=[MELEEATTACKDICE] TEXT=[1]
[07:05]  ☐ RECORD=[56] NAME=[CONFIG] DESC=[ANIMPUNCHLEFT] TEXT=[punch_l]
[07:05]  ☐ RECORD=[57] NAME=[CONFIG] DESC=[ANIMPUNCHRIGHT] TEXT=[punch_r]
[07:05]  ☐ RECORD=[58] NAME=[CONFIG] DESC=[ANIMPUNCHONETWO] TEXT=[punch_onetwo]
[07:05]  ☐ RECORD=[59] NAME=[CONFIG] DESC=[ANIMKICK] TEXT=[kick_roundhouse_r]
[07:05]  ☐ RECORD=[60] NAME=[CONFIG] DESC=[SINGLEPUNCHDELAY] TEXT=[1]
[07:05]  ☐ RECORD=[61] NAME=[CONFIG] DESC=[DOUBLEPUNCHDELAY] TEXT=[2]
[07:05]  ☐ RECORD=[62] NAME=[CONFIG] DESC=[KICKDELAY] TEXT=[3]
[07:05]  ☐ RECORD=[63] NAME=[CONFIG] DESC=[FIELDOFATTACK] TEXT=[3.141593]
[07:05]  ☐ RECORD=[64] NAME=[ARMOR] DESC=[POWER] TEXT=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
[07:05]  ☐ RECORD=[65] NAME=[MYRIAD] DESC=[MAXSTAT] TEXT=[10]
[07:05]  ☐ RECORD=[66] NAME=[MYRIAD] DESC=[MINSKILL] TEXT=[0]
[07:05]  ☐ RECORD=[67] NAME=[MYRIAD] DESC=[MAXSKILL] TEXT=[7]
[07:05]  ☐ RECORD=[68] NAME=[MYRIAD] DESC=[MINEFFECT] TEXT=[0]
[07:05]  ☐ RECORD=[69] NAME=[MYRIAD] DESC=[MAXEFFECT] TEXT=[5]
[07:05]  ☐ RECORD=[70] NAME=[MYRIAD] DESC=[MINRESILIENCE] TEXT=[0]
[07:05]  ☐ RECORD=[71] NAME=[MYRIAD] DESC=[MAXRESILIENCE] TEXT=[20]
[07:05]  ☐ RECORD=[72] NAME=[MYRIAD] DESC=[MINBOON] TEXT=[0]
[07:05]  ☐ RECORD=[73] NAME=[MYRIAD] DESC=[MAXBOON] TEXT=[5]
[07:05]  ☐ RECORD=[74] NAME=[MYRIAD] DESC=[MINFLAW] TEXT=[0]
[07:05]  ☐ RECORD=[75] NAME=[MYRIAD] DESC=[MAXFLAW] TEXT=[5]
[07:05]  ☐ RECORD=[76] NAME=[MYRIAD] DESC=[MINRP] TEXT=[0]
[07:05]  ☐ RECORD=[77] NAME=[MYRIAD] DESC=[MAXRP] TEXT=[10]
[07:05]  ☐ RECORD=[78] NAME=[MYRIAD] DESC=[MINITEM] TEXT=[0]
[07:05]  ☐ RECORD=[79] NAME=[MYRIAD] DESC=[MAXITEM] TEXT=[100]
[07:05]  ☐ RECORD=[80] NAME=[MYRIAD] DESC=[MINARMOR] TEXT=[0]
[07:05]  ☐ RECORD=[81] NAME=[MYRIAD] DESC=[MAXARMOR] TEXT=[5]
[07:05]  ☐ RECORD=[82] NAME=[MYRIAD] DESC=[MINDAMAGE] TEXT=[0]
[07:05]  ☐ RECORD=[83] NAME=[MYRIAD] DESC=[MAXDAMAGE] TEXT=[5]