It is currently October 22nd, 2019, 6:09 pm

Storing an Array or Table in a Single Key

Tips and Tricks from the Rainmeter Community
User avatar
killall-q
Posts: 285
Joined: August 14th, 2009, 8:04 am

Storing an Array or Table in a Single Key

killall-q » June 3rd, 2014, 6:31 pm

Sometimes you have a skin that requires storing an arbitrary, or large, or possibly unlimited number of values; a notes or calendar skin, for example.

The Classic Solution

You can merely have a key for every value you want to store.

SomeVariables.inc

Code: Select all

[Variables]
Key1=
Key2=
Key3=
...
Retrieving that data consists of:

Code: Select all

[Variables]
@Include=#CURRENTPATH#SomeVariables.inc

[MeterNote1]
Meter=String
Text=#Key1#
Storage consists of:

Code: Select all

!WriteKeyValue Variables Key1 "#UserInput#" "#CURRENTPATH#SomeVariables.inc"
Or for access without refreshing:

Code: Select all

[!SetVariable Key1 "#UserInput#"][!WriteKeyValue Variables Key1 "#UserInput#" "#CURRENTPATH#SomeVariables.inc"]
Sparse Array

You can use delimiters in values to store multiple in CSV (comma separated value) format.

A live example: In my GW2 World Boss Timer, there are 96 15-minute time slots in a day, and I want to mark any one or multiple of them for audio alerts. Instead of storing 96 keys, I use 1 key:

Code: Select all

AlertSlot=,65,5,24,
Note that the delimiter bookends the values in addition to separating them. This is necessary to ensure that when I look for "4", it doesn't find it in "24" and return a false positive. Also note that the values do not need to be sorted.


Retrieval works as follows:

Code: Select all

function Initialize()
   alertSlot = SKIN:GetVariable('AlertSlot')
end

function Alert()
   if alertSlot:find(','..currSlot..',') then
      SKIN:Bang('Play "#SoundFile#"')
   end
end
Storage works as follows:

Code: Select all

function SetAlert(n)
   alertSlot = alertSlot:find(','..currSlot..',') and alertSlot:gsub(','..currSlot..',', ',') or alertSlot..currSlot..','
   SKIN:Bang('!WriteKeyValue Variables AlertSlot "'..alertSlot..'" "#CURRENTPATH#Settings.inc"')
end
In this particular case, I am toggling the value. If it is found, it is erased by replacing the value and its surrounding commas with a single comma. If it is not found, it is appended to the end of the whole string with a bookend comma.

Also, in this application, alertSlot was only used in Lua, so I did not bother using !SetVariable. Rainmeter will only show AlertSlot as it was when it was first loaded, while I keep the equivalent Lua variable up-to-date.

Sparse Matrix

For, say, a calendar with notes, you will need those values to be indexed, requiring a 2D, 3D, or xD array. This is easily done with 2 delimiters.

Code: Select all

Notes=¦2014·6·3·"Buy milk, cookies."¦2014·6·25·"Chemotherapy 2pm"¦
Note that commas don't make such good delimiters once user input is involved, and that while the values now have multiple indexes, there remains no need to sort.

Also note that the empty key must look like this:

Code: Select all

Notes=¦
Retrieval:

Code: Select all

function Initialize()
   notes = SKIN:GetVariable('Notes')
end

function DisplayCalendar(year, month)
   for day = 1, NumDays(month) do
      if notes.find('¦'..year..'·'..month..'·'..day..'·.*¦') then
         SKIN:Bang('!SetOption MeterDay'..day..'Note Text '..notes.match('¦'..year..'·'..month..'·'..day..'·(.*)¦'))
      end
   end
   SKIN:Bang('!Update')
end
Storage:

Code: Select all

function SetNote(year, month, day, note)
   EraseNote(year, month, day)
   if note ~= "" then
      notes = notes..year..'·'..month..'·'..day..'·"'..note..'"¦'
      SKIN:Bang('!WriteKeyValue', 'Variables', 'Notes', notes)
   end
end
Erasure:

Code: Select all

function EraseNote(year, month, day)
   notes = notes:gsub('¦'..year..'·'..month..'·'..day..'·.*¦', '¦')
   SKIN:Bang('!WriteKeyValue', 'Variables', 'Notes', notes)
end
It would take just a little more work to add (and order) notes by time, fill the input box with the original note when editing, etc.