Skip to content

Commit 810aec1

Browse files
committed
Fix #98: Remove duplicate constants and update references to UIConstants/AppConstants + Improve thread management for better stability and shutdown handling
1 parent 5bb5b0b commit 810aec1

File tree

3 files changed

+77
-71
lines changed

3 files changed

+77
-71
lines changed

BabbleApp/babbleapp.py

Lines changed: 77 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,6 @@
5252
# Random environment variable to speed up webcam opening on the MSMF backend.
5353
# https://github.com/opencv/opencv/issues/17687
5454
os.environ["OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS"] = "0"
55-
56-
WINDOW_NAME = "Babble App"
57-
CAM_NAME = "-CAMWIDGET-"
58-
SETTINGS_NAME = "-SETTINGSWIDGET-"
59-
ALGO_SETTINGS_NAME = "-ALGOSETTINGSWIDGET-"
60-
CALIB_SETTINGS_NAME = "-CALIBSETTINGSWIDGET-"
61-
CAM_RADIO_NAME = "-CAMRADIO-"
62-
SETTINGS_RADIO_NAME = "-SETTINGSRADIO-"
63-
ALGO_SETTINGS_RADIO_NAME = "-ALGOSETTINGSRADIO-"
64-
CALIB_SETTINGS_RADIO_NAME = "-CALIBSETTINGSRADIO-"
65-
6655
page_url = "https://github.com/Project-Babble/ProjectBabble/releases/latest"
6756
appversion = "Babble v2.0.7"
6857

@@ -102,30 +91,56 @@ async def check_for_updates(config, notification_manager):
10291
f'\033[93m[{lang._instance.get_string("log.info")}] {lang._instance.get_string("babble.needUpdateOne")} [{appversion}] {lang._instance.get_string("babble.needUpdateTwo")} [{latestversion}] {lang._instance.get_string("babble.needUpdateThree")}.\033[0m'
10392
)
10493
await notification_manager.show_notification(appversion, latestversion, page_url)
94+
10595
except requests.exceptions.Timeout:
10696
print(f'[{lang._instance.get_string("log.info")}] {lang._instance.get_string("babble.updateTimeout")}')
10797
except requests.exceptions.HTTPError as e:
10898
print(f'[{lang._instance.get_string("log.info")}] {lang._instance.get_string("babble.updateHttpError")}: {e}')
10999
except requests.exceptions.ConnectionError:
110100
print(f'[{lang._instance.get_string("log.info")}] {lang._instance.get_string("babble.noInternet")}')
111101
except Exception as e:
112-
print(f'[{lang._instance.get_string("log.info")}] {lang._instance.get_string("babble.updateCheckFailed")}: {e}')
102+
print(f'[{lang._instance.get_string("log.info")}] {lang._instance.get_string("babble.updateCheckFailed")}: {e}')
103+
113104
class ThreadManager:
114-
def __init__(self):
115-
self.threads = []
116-
117-
def add_thread(self, thread):
118-
self.threads.append(thread)
105+
def __init__(self, cancellation_event):
106+
"""Initialize ThreadManager with a cancellation event for signaling threads."""
107+
self.threads = [] # List of (thread, shutdown_obj) tuples
108+
self.cancellation_event = cancellation_event
109+
self.logger = logging.getLogger("ThreadManager")
110+
111+
def add_thread(self, thread, shutdown_obj=None):
112+
"""Add a thread and its optional shutdown object to the manager."""
113+
self.threads.append((thread, shutdown_obj))
119114
thread.start()
120-
121-
def shutdown_all(self):
122-
for thread in self.threads:
123-
if hasattr(thread, 'shutdown') and callable(thread.shutdown):
124-
thread.shutdown()
125-
126-
for thread in self.threads:
115+
self.logger.debug(f"Started thread: {thread.name}")
116+
117+
def shutdown_all(self, timeout=5.0):
118+
"""Shutdown all managed threads with a configurable timeout."""
119+
self.logger.info("Initiiating shutdown of all threads")
120+
self.cancellation_event.set() # Signal all threads to stop
121+
122+
# Call shutdown methods on associated objects if available
123+
for thread, shutdown_obj in self.threads:
124+
if shutdown_obj and hasattr(shutdown_obj, 'shutdown') and callable(shutdown_obj.shutdown):
125+
try:
126+
self.logger.debug(f"Calling shutdown on {shutdown_obj}")
127+
shutdown_obj.shutdown()
128+
except Exception as e:
129+
self.logger.error(f"Error shutting down {shutdown_obj}: {e}")
130+
131+
# Join threads with the specified timeout
132+
for thread, _ in self.threads:
127133
if thread.is_alive():
128-
thread.join()
134+
self.logger.debug(f"Joining thread: {thread.name} with timeout {timeout}s")
135+
thread.join(timeout=timeout)
136+
137+
# Remove terminated threads from the list
138+
self.threads = [(t, s) for t, s in self.threads if t.is_alive()]
139+
140+
if self.threads:
141+
self.logger.warning(f"{len(self.threads)} threads still alive: {[t.name for t, _ in self.threads]}")
142+
else:
143+
self.logger.info("All threads terminated successfully")
129144

130145
async def async_main():
131146
ensurePath()
@@ -153,13 +168,12 @@ async def async_main():
153168

154169
timerResolution(True)
155170

156-
thread_manager = ThreadManager()
171+
thread_manager = ThreadManager(cancellation_event)
157172

158173
osc_queue: queue.Queue[tuple[bool, int, int]] = queue.Queue(maxsize=10)
159174
osc = VRChatOSC(cancellation_event, osc_queue, config)
160-
osc_thread = threading.Thread(target=osc.run)
161-
thread_manager.add_thread(osc_thread)
162-
175+
osc_thread = threading.Thread(target=osc.run, name="OSCThread")
176+
thread_manager.add_thread(osc_thread, shutdown_obj=osc)
163177
cams = [
164178
CameraWidget(Tab.CAM, config, osc_queue),
165179
]
@@ -177,56 +191,56 @@ async def async_main():
177191
"TABSELECTRADIO",
178192
background_color=bg_color_clear,
179193
default=(config.cam_display_id == Tab.CAM),
180-
key=CAM_RADIO_NAME,
194+
key=UIConstants.CAM_RADIO_NAME,
181195
),
182196
sg.Radio(
183197
lang._instance.get_string("babble.settingsPage"),
184198
"TABSELECTRADIO",
185199
background_color=bg_color_clear,
186200
default=(config.cam_display_id == Tab.SETTINGS),
187-
key=SETTINGS_RADIO_NAME,
201+
key=UIConstants.SETTINGS_RADIO_NAME,
188202
),
189203
sg.Radio(
190204
lang._instance.get_string("babble.algoSettingsPage"),
191205
"TABSELECTRADIO",
192206
background_color=bg_color_clear,
193207
default=(config.cam_display_id == Tab.ALGOSETTINGS),
194-
key=ALGO_SETTINGS_RADIO_NAME,
208+
key=UIConstants.ALGO_SETTINGS_RADIO_NAME,
195209
),
196210
sg.Radio(
197211
lang._instance.get_string("babble.calibrationPage"),
198212
"TABSELECTRADIO",
199213
background_color=bg_color_clear,
200214
default=(config.cam_display_id == Tab.CALIBRATION),
201-
key=CALIB_SETTINGS_RADIO_NAME,
215+
key=UIConstants.CALIB_SETTINGS_RADIO_NAME,
202216
),
203217
],
204218
[
205219
sg.Column(
206220
cams[0].widget_layout,
207221
vertical_alignment="top",
208-
key=CAM_NAME,
222+
key=UIConstants.CAM_NAME,
209223
visible=(config.cam_display_id in [Tab.CAM]),
210224
background_color=bg_color_highlight,
211225
),
212226
sg.Column(
213227
settings[0].widget_layout,
214228
vertical_alignment="top",
215-
key=SETTINGS_NAME,
229+
key=UIConstants.SETTINGS_NAME,
216230
visible=(config.cam_display_id in [Tab.SETTINGS]),
217231
background_color=bg_color_highlight,
218232
),
219233
sg.Column(
220234
settings[1].widget_layout,
221235
vertical_alignment="top",
222-
key=ALGO_SETTINGS_NAME,
236+
key=UIConstants.ALGO_SETTINGS_NAME,
223237
visible=(config.cam_display_id in [Tab.ALGOSETTINGS]),
224238
background_color=bg_color_highlight,
225239
),
226240
sg.Column(
227241
settings[2].widget_layout,
228242
vertical_alignment="top",
229-
key=CALIB_SETTINGS_NAME,
243+
key=UIConstants.CALIB_SETTINGS_NAME,
230244
visible=(config.cam_display_id in [Tab.CALIBRATION]),
231245
background_color=bg_color_highlight,
232246
),
@@ -247,22 +261,22 @@ async def async_main():
247261
# the cam needs to be running before it is passed to the OSC
248262
if config.settings.gui_ROSC:
249263
osc_receiver = VRChatOSCReceiver(cancellation_event, config, cams)
250-
osc_receiver_thread = threading.Thread(target=osc_receiver.run)
251-
thread_manager.add_thread(osc_receiver_thread)
264+
osc_receiver_thread = threading.Thread(target=osc_receiver.run, name="OSCReceiverThread")
265+
thread_manager.add_thread(osc_receiver_thread, shutdown_obj=osc_receiver)
252266
ROSC = True
253267

254268
# Create the window
255269
window = sg.Window(
256270
f"{AppConstants.VERSION}", layout, icon=os.path.join('Images', 'logo.ico'), background_color=bg_color_clear )
257271

258272
# Run the main loop
259-
await main_loop(window, config, cams, settings, cancellation_event)
273+
await main_loop(window, config, cams, settings, thread_manager)
260274

261275
# Cleanup after main loop exits
262276
timerResolution(False)
263277
print(f'\033[94m[{lang._instance.get_string("log.info")}] {lang._instance.get_string("babble.exit")}\033[0m')
264278

265-
async def main_loop(window, config, cams, settings, cancellation_event):
279+
async def main_loop(window, config, cams, settings, thread_manager):
266280
tint = AppConstants.DEFAULT_WINDOW_FOCUS_REFRESH
267281
fs = False
268282

@@ -273,7 +287,8 @@ async def main_loop(window, config, cams, settings, cancellation_event):
273287
# Exit code here
274288
for cam in cams:
275289
cam.stop()
276-
cancellation_event.set()
290+
thread_manager.shutdown_all()
291+
window.close()
277292
return
278293

279294
try:
@@ -295,57 +310,57 @@ async def main_loop(window, config, cams, settings, cancellation_event):
295310
except KeyError:
296311
pass
297312

298-
if values[CAM_RADIO_NAME] and config.cam_display_id != Tab.CAM:
313+
if values[UIConstants.CAM_RADIO_NAME] and config.cam_display_id != Tab.CAM:
299314
cams[0].start()
300315
settings[0].stop()
301316
settings[1].stop()
302317
settings[2].stop()
303-
window[CAM_NAME].update(visible=True)
304-
window[SETTINGS_NAME].update(visible=False)
305-
window[ALGO_SETTINGS_NAME].update(visible=False)
306-
window[CALIB_SETTINGS_NAME].update(visible=False)
318+
window[UIConstants.CAM_NAME].update(visible=True)
319+
window[UIConstants.SETTINGS_NAME].update(visible=False)
320+
window[UIConstants.ALGO_SETTINGS_NAME].update(visible=False)
321+
window[UIConstants.CALIB_SETTINGS_NAME].update(visible=False)
307322
config.cam_display_id = Tab.CAM
308323
config.save()
309324

310-
elif values[SETTINGS_RADIO_NAME] and config.cam_display_id != Tab.SETTINGS:
325+
elif values[UIConstants.SETTINGS_RADIO_NAME] and config.cam_display_id != Tab.SETTINGS:
311326
cams[0].stop()
312327
settings[1].stop()
313328
settings[2].stop()
314329
settings[0].start()
315-
window[CAM_NAME].update(visible=False)
316-
window[SETTINGS_NAME].update(visible=True)
317-
window[ALGO_SETTINGS_NAME].update(visible=False)
318-
window[CALIB_SETTINGS_NAME].update(visible=False)
330+
window[UIConstants.CAM_NAME].update(visible=False)
331+
window[UIConstants.SETTINGS_NAME].update(visible=True)
332+
window[UIConstants.ALGO_SETTINGS_NAME].update(visible=False)
333+
window[UIConstants.CALIB_SETTINGS_NAME].update(visible=False)
319334
config.cam_display_id = Tab.SETTINGS
320335
config.save()
321336

322337
elif (
323-
values[ALGO_SETTINGS_RADIO_NAME]
338+
values[UIConstants.ALGO_SETTINGS_RADIO_NAME]
324339
and config.cam_display_id != Tab.ALGOSETTINGS
325340
):
326341
cams[0].stop()
327342
settings[0].stop()
328343
settings[2].stop()
329344
settings[1].start()
330-
window[CAM_NAME].update(visible=False)
331-
window[SETTINGS_NAME].update(visible=False)
332-
window[ALGO_SETTINGS_NAME].update(visible=True)
333-
window[CALIB_SETTINGS_NAME].update(visible=False)
345+
window[UIConstants.CAM_NAME].update(visible=False)
346+
window[UIConstants.SETTINGS_NAME].update(visible=False)
347+
window[UIConstants.ALGO_SETTINGS_NAME].update(visible=True)
348+
window[UIConstants.CALIB_SETTINGS_NAME].update(visible=False)
334349
config.cam_display_id = Tab.ALGOSETTINGS
335350
config.save()
336351

337352
elif (
338-
values[CALIB_SETTINGS_RADIO_NAME]
353+
values[UIConstants.CALIB_SETTINGS_RADIO_NAME]
339354
and config.cam_display_id != Tab.CALIBRATION
340355
):
341356
cams[0].start() # Allow tracking to continue in calibration tab
342357
settings[0].stop()
343358
settings[1].stop()
344359
settings[2].start()
345-
window[CAM_NAME].update(visible=False)
346-
window[SETTINGS_NAME].update(visible=False)
347-
window[ALGO_SETTINGS_NAME].update(visible=False)
348-
window[CALIB_SETTINGS_NAME].update(visible=True)
360+
window[UIConstants.CAM_NAME].update(visible=False)
361+
window[UIConstants.SETTINGS_NAME].update(visible=False)
362+
window[UIConstants.ALGO_SETTINGS_NAME].update(visible=False)
363+
window[UIConstants.CALIB_SETTINGS_NAME].update(visible=True)
349364
config.cam_display_id = Tab.CALIBRATION
350365
config.save()
351366

BabbleApp/eval "$(ssh-agent -s)"

Lines changed: 0 additions & 8 deletions
This file was deleted.

BabbleApp/eval "$(ssh-agent -s)".pub

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)