It is currently November 30th, 2022, 7:16 am

Doesn't the global variable work in Rainmeter's Lua?

Discuss the use of Lua in Script measures.
User avatar
Yincognito
Rainmeter Sage
Posts: 4817
Joined: February 27th, 2015, 2:38 pm
Location: Terra Yincognita

Doesn't the global variable work in Rainmeter's Lua?

Post by Yincognito »

Straight to the point...

[SkinFolder]\@Resources\Script.lua:

Code: Select all

function GetVar(name)
  x = 10
  return _G[name]
end
[SkinFolder]\Test.ini:

Code: Select all

[Variables]

[Rainmeter]
Update=1000
DynamicWindowSize=1
AccurateText=1
BackgroundMode=2
SolidColor=47,47,47,255

---Measures---

[MeasureScript]
Measure=Script
ScriptFile=#@#Script.lua
UpdateDivider=-1

---Meters---

[MeterTest]
Meter=String
FontFace=Consolas
FontColor=255,255,255,255
Padding=5,5,5,5
FontSize=16
AntiAlias=1
Text="Script = [&MeasureScript:GetVar('x')]"
UpdateDivider=-1
DynamicVariables=1
Result:
Global.jpg
So, is there any particularity that means one can't work with the _G global variable within the Rainmeter - Lua system? For the record, I'm just trying to set up a function to use different variables according to the string name of the variable that I pass on from Rainmeter. Thus, _G['x'] should mean variable x, which could further be used to do things in the code accordingly. Thing is, this returns nil.

The reason why I didn't post this under the Bugs & Feature Suggestions section of the forum is because I have no idea if this is expected / normal to happen or not. I read some articles on the internet regarding similar situations, but not sure if they apply to Rainmeter...

P.S. By the way, this happens in the Initialize() function as well.
You do not have the required permissions to view the files attached to this post.
User avatar
death.crafter
Rainmeter Sage
Posts: 1398
Joined: April 24th, 2021, 8:13 pm

Re: Doesn't the global variable work in Rainmeter's Lua?

Post by death.crafter »

Actually, no. You can't do that. As every script measure has it's own environment, which is not global.

If you want to declare a true global variable, the use loadstring e.g.
loadstring("varname = " .. value)()

What you are trying to do can be done using [&Measure:VariableName], but of you still want a function to retrieve it you can do something like,

Code: Select all

function dostring(str)
    local f = loadstring(str)
    setfenv(f, getfenv())
    return f
end
function GetVar(varname)
    return dostring(''return " .. varname)()
end
or even better without the complications,

Code: Select all

function GetVar(varname)
    local selfName = SELF:GetName() -- I'm not sure, but there is a method like this
    return SKIN: ReplaceVariables(
        ''[&" .. selfName .. '':" .. varname .. "]"
    )
end
from the Realm of Death
User avatar
Brian
Developer
Posts: 2535
Joined: November 24th, 2011, 1:42 am
Location: Utah

Re: Doesn't the global variable work in Rainmeter's Lua?

Post by Brian »

EDIT: death.crafter beat me to it, however I see no reason to "force" setting the variables to global.

While I am not 100%, I think this is because each measure runs in its own "environment". So the _G variable is look at the "global" environment, not the "measure" environment.

What you want is something similar to "this" that is used in other programming languages. You can accomplish this in 2 ways.

#1:

Code: Select all

function GetVar(name)
  x = 10
  setfenv(1, _G)
  return tostring(_G[name])
end
#2:

Code: Select all

function GetVar(name)
  x = 10
  local env = getfenv()
  return env[name]
I prefer method #2 over #1 since you are just retrieving the current environment over explicitly setting the environment. The 1 environment is the current environment, while the 0 environment is always the "global" environment.

Note: I placed a tostring in #1 since there is an error thrown about an invalid "return" type. I think this might happen on the very first load and might be a side effect of setfenv.

Note 2: In #2, getfenv() should really be getfenv(1), but the 1 is default, so I omitted it.

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

Re: Doesn't the global variable work in Rainmeter's Lua?

Post by Yincognito »

death.crafter wrote: February 25th, 2022, 11:54 pmActually, no. You can't do that. As every script measure has it's own environment, which is not global.
I don't like to take no for an answer, but for now, that surely explains the behavior. Thanks for making it clear. :thumbup:
death.crafter wrote: February 25th, 2022, 11:54 pmIf you want to declare a true global variable, the use loadstring e.g.
loadstring("varname = " .. value)()

What you are trying to do can be done using [&Measure:VariableName], but of you still want a function to retrieve it you can do something like,

Code: Select all

function dostring(str)
    local f = loadstring(str)
    setfenv(f, getfenv())
    return f
end
function GetVar(varname)
    return dostring(''return " .. varname)()
end
or even better without the complications,

Code: Select all

function GetVar(varname)
    local selfName = SELF:GetName() -- I'm not sure, but there is a method like this
    return SKIN: ReplaceVariables(
        ''[&" .. selfName .. '':" .. varname .. "]"
    )
end
Yep, the loadstring() was the first option I thought of, even before _G, but in my tests a while ago when I was excited about the function, I forgot to use the () at the end of your syntax to make it clear that it was a basic function. So, I wrongly rated the method unfeasible because while it worked for "operations" that already resulted in "something", it seemed to fail for simple "passive" stuff like a print() or getting a table dimension (the latter actually being my usage scenario now, like shown below). Many thanks for the working method and also for making me realize what I was doing wrong before in this case. :thumbup:
Brian wrote: February 26th, 2022, 12:08 am EDIT: death.crafter beat me to it, however I see no reason to "force" setting the variables to global.

While I am not 100%, I think this is because each measure runs in its own "environment". So the _G variable is look at the "global" environment, not the "measure" environment.

What you want is something similar to "this" that is used in other programming languages. You can accomplish this in 2 ways.

#1:

Code: Select all

function GetVar(name)
 x = 10
 setfenv(1, _G)
 return tostring(_G[name])
end
#2:

Code: Select all

function GetVar(name)
 x = 10
 local env = getfenv()
 return env[name]
I prefer method #2 over #1 since you are just retrieving the current environment over explicitly setting the environment. The 1 environment is the current environment, while the 0 environment is always the "global" environment.

Note: I placed a tostring in #1 since there is an error thrown about an invalid "return" type. I think this might happen on the very first load and might be a side effect of setfenv.

Note 2: In #2, getfenv() should really be getfenv(1), but the 1 is default, so I omitted it.

-Brian
Death.crafter provided excellent choices and his 1st method works (the 2nd doesn't probably because Rainmeter can only handle strings, numbers and booleans in its Lua framework) but so does your 2nd, which is slightly more compact and closer to what I envisioned (bar the _G usage). Below, there's a summary of yours and death.crafter's methods and their success rate - many thanks for your last one, very easy to apply, and probably my favorite. :great:

Also, the invalid return type is NOT a side effect of setfenv and it's NOT specific only to the first load, it has been annoying me for a while, which is why all my functions (which are actually like the "procedures" in other languages in that they don't return anything, they just "do" stuff) have a return true at the end to get rid of the error. I let a test of the no return function errors at the end for you to investigate if you like (just comment the update dividers in the INI to see that's it's not a load problem)...
Lua:

Code: Select all

function Initialize()
  tableone = {1, 2, 3}
  tabletwo = {4, 5}
end

-- 1. death.crafter method 1 works! THANKS!

-- function dostring(str)
  -- local f = loadstring(str)
  -- setfenv(f, getfenv())
  -- return f
-- end

-- function GetCount(tablename)
  -- dostring('print(' .. tablename .. ')')()
  -- return dostring('return #' .. tablename)()
-- end

-- 2. death.crafter method 2 doesn't work

-- function GetCount(tablename)
  -- tablevar = SKIN:ReplaceVariables('[&' .. SELF:GetName() .. ':' .. tablename .. ']')
  -- print(tablevar)
  -- return #tablevar
-- end

-- 3. Brian method 1 doesn't work

-- function GetCount(tablename)
  -- setfenv(1, _G)
  -- print(_G[tablename])
  -- return #_G[tablename]
-- end

-- 4. Brian method 2 works as well! THANKS!

-- function GetCount(tablename)
  -- local env = getfenv(1)
  -- print(env[tablename])
  -- return #env[tablename]
-- end
  
-- CONCLUSION: I think I'll settle for Brian's method 2, more compact and straightforward

-- X. No Return Function Error Sample

function GetCount(x)
  print('a')
end
Ini:

Code: Select all

[Variables]

[Rainmeter]
Update=1000
DynamicWindowSize=1
AccurateText=1
BackgroundMode=2
SolidColor=47,47,47,255

---Measures---

[Script]
Measure=Script
ScriptFile=#@#Script.lua
UpdateDivider=-1

---Meters---

[TCount]
Meter=String
FontFace=Consolas
FontColor=255,255,255,255
Padding=5,5,5,5
FontSize=16
AntiAlias=1
Text="TCount = [&Script:GetCount('tableone')]"
UpdateDivider=-1
DynamicVariables=1
User avatar
Brian
Developer
Posts: 2535
Joined: November 24th, 2011, 1:42 am
Location: Utah

Re: Doesn't the global variable work in Rainmeter's Lua?

Post by Brian »

Yincognito wrote: February 26th, 2022, 1:33 am

Code: Select all

-- X. No Return Function Error Sample

function GetCount(x)
  print('a')
end
I'm not really seeing an issue in this particular example since the error message is justified since you are calling the function from an "inline" source. Meaning, it is expecting "something" to return. It is supposed to represent a variable after all.

If you just want to run a particular chunk of code, you can use !CommandMeasure to accomplish that. Something like:
OnUpdateAction=[!CommandMeasure Script "print('a')"] or
OnUpdateAction=[!CommandMeasure Script "GetCount(something)"]

I think I was seeing that error message mainly because the value returned was "nil" at that time. I guess a better "return" would be something like:
return _G[name] or 0 or something similar for that particular example.

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

Re: Doesn't the global variable work in Rainmeter's Lua?

Post by Yincognito »

Brian wrote: February 26th, 2022, 7:50 am I'm not really seeing an issue in this particular example since the error message is justified since you are calling the function from an "inline" source. Meaning, it is expecting "something" to return. It is supposed to represent a variable after all.
Oh, that explains it - never thought it that way. The truth is that I overuse (and abuse, LOL) the inline Lua syntax, since it seems simpler and more compact to me (the only "odd" part is the nested syntax marker, but I got used to it anyway). Now that you said it, I realized I never used [!CommandMeasure ...] in Lua's case. Thanks for explaining it, it makes sense. I don't think I'll start using commanding measures for Lua from now on as a result, but at least I know why this happens, have an alternative way to do it, and also a workaround if I still go the inline way.
Brian wrote: February 26th, 2022, 7:50 amI guess a better "return" would be something like:
return _G[name] or 0 or something similar for that particular example.
Indeed. I use this tactic often as well, just didn't bothered to do so for a test sample, where the actual value and error is more important to know than having error exception handling.