Possible to use multiple instances?
System: Windows 10 Pro x64 Build 15063.413
Rainmeter: 4.0.0 r2746 x64
I just tested this plugin out successfully with a single measure + meter combo. However, Rainmeter crashes immediately without logging any errors when I try to create a second set (separate measure + meter).
Both python-script instances are essentially identical aside from the file & class names, which are unique & properly isolated.
Here's a simplified version of the .ini file:
[PY_Test_A]
Measure = Plugin
Plugin = Python
PythonHome = C:\Python\3
ScriptPath = Test_A.py
ClassName = Test_A
UpdateDivider = 1
[Test_A]
Meter = String
MeasureName = PY_Test_A
[PY_Test_B]
Measure = Plugin
Plugin = Python
PythonHome = C:\Python\3
ScriptPath = Test_B.py
ClassName = Test_B
UpdateDivider = 1
[Test_B]
Meter = String
MeasureName = PY_Test_B
And here is the simplified code for Test_A.py & Test_B.py:
class Test_X:
def GetString(self):
return f"SUCCESS @ {self.__class__.__name__}"
def Reload(self, rm, maxValue): pass
def ExecuteBang(self, command): pass
def Update(self): pass
def Finalize(self): pass
This is something that I broke by removing the sub-interpreter code when trying to fix the original version. I was able to get it to run, but with severely limited functionality - i.e., with only one plugin.
Looking back, I suspect the correct way to deal with this is to spawn a new process for each Python interpreter. Python is really bad at handling multiple interpreters in the same process. I am just not familiar enough with multiprocessing in C++ to be able to do that right off the bat.
I'll add this to my rainy-day projects list, but if someone else wants to take a crack at it first, feel free to submit a pull request!
@glitchassassin
Any idea what the performance would be like with multiple interpreters? (around 10-100ish)
I set up a small framework last night based on using a single instance, but then it occurred to me that I could also subclass it for some minimal single-purpose measures (which is when I ran into the issue).
Single Instance
This setup's intention is to outsource all data-work to Python, and allow a single line point of usage (python_utils.execute) to retreive data in Lua. This is acheived with a string property which is paired to GetString, an output decorator which sets string, and Bang("!CommandMeasure"...) passing an output-wrapped function string to be run by exec @ ExecuteBang.
@ measure.py
### StdLib ###
import subprocess
from functools import wraps
SUBLIME_TEXT = "C:/Program Files/Sublime Text/sublime_text.exe"
class Measure:
string = ""
####### Rainmeter Functions ################################################################################
def Reload(self, rainmeter, maxValue):
self.rainmeter = rainmeter
def ExecuteBang(self, command):
try:
exec(f"self.{command}")
except Exception as e:
self.notify(e)
def GetString(self):
return self.string
def Update(self):
pass
def Finalize(self):
pass
####### Output Functions ###################################################################################
@output
def get_CurrentTime(self):
return datetime.now().strftime("%I:%M:%S %p")
####### Utils ##############################################################################################
def log(self, message, ):
self.rainmeter.RmLog(self.rainmeter.LOG_ERROR, str(message))
def notify(self, message):
subprocess.Popen([SUBLIME_TEXT, "--new-window", "--command", f"insert {{\"characters\": \"{message}\"}}"])
####### Output Decorator ###################################################################################
def output(function):
@wraps(function)
def wrapped(self, *args, **kwargs):
result = function(self, *args, **kwargs)
self.string = str(result)
return wrapped
@ MySkin.lua
--------- Example Usage ------------------------------------------------------------------------------------
function Update()
time = python_utils.execute{script="PyMeasure", command="get_CurrentTime()"}
meter_utils.set_Text{meter="CurrentTime", text=time}
end
--------- Utils --------------------------------------------------------------------------------------------
python_utils = {}
function python_utils.execute(ARGS) -- script, command
measure = SKIN:GetMeasure(ARGS.script)
SKIN:Bang("!CommandMeasure", ARGS.script, ARGS.command)
return measure:GetStringValue()
end
meter_utils = {}
function meter_utils.set_Text(ARGS) -- meter, text
SKIN:Bang("!SetOption", ARGS.meter, "Text", ARGS.text)
end
Multiple Instances
This setup builds upon the single instance setup by subclassing Measure, and allows concise python implementations that can be baked into INI files without the need for Lua.
@ CurrentTime.py:
### Skin Framework ###
from measure import Measure, output
### StdLib ###
from datetime import datetime
class CurrentTime(Measure):
@output
def Update(self):
return datetime.now().strftime("%I:%M:%S %p")