Possible to use multiple instances?
Opened this issue · 2 comments
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!
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")