It is currently March 29th, 2020, 12:19 am

How a skin is updated

Our most popular Tips and Tricks from the Rainmeter Team and others
User avatar
jsmorley
Developer
Posts: 20425
Joined: April 19th, 2009, 11:02 pm
Location: Fort Hunt, Virginia, USA

How a skin is updated

Post by jsmorley »

This is a somewhat technical outline of what steps are taken during the Update cycle of a skin in Rainmeter.
This information is courtesy of Brian.

On every Update cycle of each skin

- Increase persistent internal skin counter (this is the "Counter" function used in a Calc measure)

- If the skin has measures:
- If the skin has 'Net' measures:
- Update interface tables and stats
- For each measure (order is the same as they appear in the .ini file):
- If measure's update counter is >= to UpdateDivider, update the measure, otherwise skip to next measure
- If measure has dynamic variables:
- Re-read the measure options
- If paused, do nothing and skip to next measure
- If disabled:
- Set number value of measure to 0
- Skip to next measure
- Update measure's value (each measure does this differently)
- Calculate and keep track of measure's min/max values if needed
- Calculate and keep track of measure's average values if needed
- Calculate and keep track of measure's median values if needed
- Re-read any ifcondition/ifmatch options (in case any condition references itself, you want the latest value)
- Execute any if actions in the this order: IfEqual, IfAbove, IfBelow, IfCondition, and IfMatch
- Increase measure's update counter
- If measure updated correctly:
- Execute OnUpdateAction then OnChangeAction if needed

- Update the About/Skins dialog with new values

- For each meter (order is the same as they appear in the .ini file):
- Update the meter:
- If meter's update counter is >= to UpdateDivider, update the meter, otherwise skip to next meter
- If meter has dynamic variables:
- Re-read the meter options
- Retrieve and hold on to any values from measures "bound" to the meter
- Perform any substitutions on the "bound" measure's value
- Update any tooltip information
- Increase meter's update counter
- If the meter was updated successfully:
- Execute OnUpdateAction if needed

- Redraw the skin window:
- Create internal drawing objects if needed
- Draw background options (depends on BackgroundMode, could be a image, solid color, etc.) if needed
- Draw bevel options if needed
- For each meter:
- Apply TransformationMatrix if needed
- Draw meter using current values (each meter has its own rules)
- Reset transform if needed
- Update window settings (transparency, dynamic window size, etc.)

- Start/stop any transition timers (FadeIn/Out transparency options)
- Execute any OnUpdateAction from the [Rainmeter] section if needed



Other Information

Mouse Actions

Mouse actions (MouseOverAction, LeftMouseUpAction, etc.) are "interrupt" driven, and will be executed immediately by the skin. They will take whatever action they are directed to, and then the skin will resume where it left off in the cycle. If !Update or !Refresh bangs are used in the actions, then the cycle will start over at the top, rather than resuming.

Variables

Note: We read and store everything from the .ini in key=value string tables because most options can contain a string that represents some kind of a variable (#Var# or [SectionVar] etc). This includes invalid options because we do not validate when reading the .ini file, only when the skin/measures/meters are created. We keep these string tables separate from the objects they represent in case objects need to be restored to their original values, or we need to substitute variables, etc. This operation is done in the beginning when the skin is read. This is important in understanding why a variable can be replaced by another value when needed (usually in the update cycle when using DynamicVariables=1).

Once the skin is read from the .ini file, we start to create skin/measure/meter objects. The object will try to find the option it wants in string tables discussed above. If the option is not found, the object may look for the option within a 'style" (aka MeterStyle). If the option is still not found, a default value is provided. Every option has a default value even if it is an empty string. Once the option has been found and is not an empty string, it expands any environment variables first (ex. %APPDATA%). Environment variables are parsed from the start of the string to the end. Next it looks for any regular variables (#Var#), skipping any escaped variables (#*Var*#). These are also parsed from the start of the string to the end. If the name of the variable does not exist, nothing is replaced for that variable. Next it looks for any section variables (ie. measure names), skipping any escaped variables ([*MeasureName*]). These are also parsed from the start of the string to the end. Nothing is replaced if the name of the measure is invalid. Action options do not evaluate a [SectionVar] until the action is executed (this does not apply to #Vars#).

If object is asking for a numerical value, it looks for a '(' in the first position in the string. If found, it assumes the string represents a formula, and the string is passed to the math parser and parsed - Returning the result. That result is then converted to the correct 'type' asked for (integer, float, double, etc.) If that first character is not a '(', then the entire string is just converted to a number of the correct type. The conversion is done by the C runtime library, not our math parser. If any problems are encountered, the default value is returned.

So basically variables are replaced in this order:
Regular Options:
%Environment%, #Var#, [SectionVar:] (then formulas)

Action Options:
On reading objects (at first and on DV=1): %Environment%, #Var#
On execution of action: [SectionVar:], %Mouse% (for click options only)


Substitute

Substitute is performed when referencing a measure. This happens at different times, but mostly on the update cycle when a meter asks for a measures value. This also happens when a section variable is parsed and replaced. Also, during reading of tool tip options (because of percent replacement). Some meters also explicitly ask for its measure's value for whatever reasons. When IfMatch is executed, it will check for any [SectionVar]'s and will result in Substitute being called. OnChangeAction on a measure will also check a Substitute. Note: IfMatch and OnChangeAction only check for Substitute for measures that support a string value (ie. DiskSpace, Registry, Script, String, Time, Uptime, and some Plugins).
User avatar
Yincognito
Posts: 1070
Joined: February 27th, 2015, 2:38 pm
Location: Terra Yincognita

Re: How a skin is updated

Post by Yincognito »

Very useful description of what's going on in Rainmeter. I especially appreciate the Substitute bit, as in the past it took me a while to realize that Substitute is executed after IfMatch. Back then, I thought the reverse was happening (it seemed more logical, not knowing the things mentioned here), and wondered why on earth IfMatch didn't match the pattern that was supposedly being present in the string after the appropriate regexp Substitution.

Good job! :thumbup:
User avatar
jsmorley
Developer
Posts: 20425
Joined: April 19th, 2009, 11:02 pm
Location: Fort Hunt, Virginia, USA

Re: How a skin is updated

Post by jsmorley »

Yincognito wrote:
March 3rd, 2020, 12:29 pm
Very useful description of what's going on in Rainmeter. I especially appreciate the Substitute bit, as in the past it took me a while to realize that Substitute is executed after IfMatch. Back then, I thought the reverse was happening (it seemed more logical, not knowing the things mentioned here), and wondered why on earth IfMatch didn't match the pattern that was supposedly being present in the string after the appropriate regexp Substitution.

Good job! :thumbup:
I'm not sure I follow. IfMatch causes the Substitution to be executed before the match is evaluated.

Code: Select all

[Rainmeter]
Update=1000
DynamicWindowSize=1
AccurateText=1

[Variables]

[MeasureString]
Measure=String
String=One
RegExpSubstitute=1
Substitute="^One$":"Two"
IfMatch=Two
IfMatchAction=[!SetOption MeterOne FontColor 255,30,30,255]

[MeterOne]
Meter=String
MeasureName=MeasureString
FontSize=20
FontWeight=400
SolidColor=47,47,47,255
Padding=5,5,5,5
AntiAlias=1
User avatar
Yincognito
Posts: 1070
Joined: February 27th, 2015, 2:38 pm
Location: Terra Yincognita

Re: How a skin is updated

Post by Yincognito »

jsmorley wrote:
March 3rd, 2020, 12:48 pm
I'm not sure I follow. IfMatch causes the Substitution to be executed before the match is evaluated.
Then how can you explain this, for example (I have a few such measures - simplified here - in my skins):

Code: Select all

[Rainmeter]
Update=1000
DynamicWindowSize=1
AccurateText=1

[Variables]

[MeasureAdapterDescription]
Measure=Plugin
Plugin=SysInfo
SysInfoType=ADAPTER_DESCRIPTION
SysInfoData=999
RegExpSubstitute=1
Substitute="(?:^\s+|\s+$)":"","(^$|^0$)":"Unavailable"
IfCondition=1
IfTrueAction=[!SetOption MeterOne FontColor 30,255,30,255]
IfConditionMode=1
IfMatch=^.{2,}$
IfMatchAction=[!SetOption MeterOne FontColor 30,30,255,255]
IfNotMatchAction=[!SetOption MeterOne FontColor 255,30,30,255]
IfMatchMode=1
DynamicVariables=1

[MeterOne]
Meter=String
MeasureName=MeasureAdapterDescription
FontSize=20
FontWeight=400
SolidColor=47,47,47,255
Padding=5,5,5,5
AntiAlias=1
If the Substitute was executed before IfMatch was evaluated, the color of the (more than 2 chars in length) text would have been blue. Instead, it is red, meaning the text is less than 2 chars in length - something consistent with what would happen if IfMatch was evaluated before the Substitute. Or, it could be some SysInfo "delay" a la WebParser (i.e. initial value "", populated after a while) - I don't know.

I've written these kind of measures a couple of years back, so at first I thought I remembered this the wrong way, but apparently I did remember it correctly, because that (i.e. that IfMatch is succeeded by Substitute) is the logical conclusion seeing this behavior...unless you have another explanation for it, that is.

EDIT: Ok, so what I noticed is that in the case of "regular" / "built-in" measures (e.g. String, FreeDiskSpace, etc.) the Substitute is indeed executed before IfMatch is evaluated. However, for other measures (e.g. Registry, probably WebParser as well) and plugins (e.g. SysInfo, but there may be others), while the order of operations might still be "Substitute before IfMatch", an empty string return ("") of that measure/plugin causes IfMatch to get/use the original return of the measure/plugin (i.e. "") and not the susbstituted one (in effect, "reversing" the order of operations).
User avatar
Brian
Developer
Posts: 1967
Joined: November 24th, 2011, 1:42 am
Location: Utah

Re: How a skin is updated

Post by Brian »

This specific issue is not a Substitute or IfMatch problem. The issue is how plugins communicate with Rainmeter and when Rainmeter determines whether to use a string value vs a number value.

In this case, the SysInfo plugin is not returning any data (because SysInfoData=999 is not valid). So, Rainmeter gets nothing back from the plugin to use during the IfMatch execution and skips any Substitute.

However, when the string meter (and the about dialog) ask the measure (or plugin in this case) for its string value, and no string is returned, the measure then converts the number value to a string, then performs the Substitute.

It's quite complicated. Sometimes we want the number value only, sometimes we want the string value only. And sometimes we want just A value (check for string value first, but use number value if there is no string value).

To be completely honest with you, this is one of the numerous design flaws with Rainmeter. As functionality was added over time, small logic issues come about here and there which seem to make no sense. We try hard to minimize these flaws, and I think we do a decent job since they seem to rarely come up.

-Brian
User avatar
Yincognito
Posts: 1070
Joined: February 27th, 2015, 2:38 pm
Location: Terra Yincognita

Re: How a skin is updated

Post by Yincognito »

Brian wrote:
March 18th, 2020, 7:13 am
This specific issue is not a Substitute or IfMatch problem. The issue is how plugins communicate with Rainmeter and when Rainmeter determines whether to use a string value vs a number value.

In this case, the SysInfo plugin is not returning any data (because SysInfoData=999 is not valid). So, Rainmeter gets nothing back from the plugin to use during the IfMatch execution and skips any Substitute.

However, when the string meter (and the about dialog) ask the measure (or plugin in this case) for its string value, and no string is returned, the measure then converts the number value to a string, then performs the Substitute.

It's quite complicated. Sometimes we want the number value only, sometimes we want the string value only. And sometimes we want just A value (check for string value first, but use number value if there is no string value).

To be completely honest with you, this is one of the numerous design flaws with Rainmeter. As functionality was added over time, small logic issues come about here and there which seem to make no sense. We try hard to minimize these flaws, and I think we do a decent job since they seem to rarely come up.

-Brian
I understand. It's just that I guess I was expecting that even if there is no value returned from a measure / plugin, the Substitute / IfMatch would still operate on "" (the empty string), instead of skipping the execution, that's all. But then, you know best why things happen this way and which things could break backward compatibility and such. I don't have a problem with this behavior, since I adapted my code to suit it a couple of years back, I was just trying to help by bringing it to your attention, in case it was related to the issue discussed in the other thread.

I appreciate the code bits you linked to and I completely understand the logic of illogic issues (to paraphrase jsmorley in another thread) appearing over time in the development of a software. It's not that complicated and it's a perfectly natural consequence of the "evolution" of a program.