Patch_5
This commit is contained in:
@ -3,7 +3,6 @@ import os
|
||||
import queue
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import types
|
||||
from typing import Dict, Tuple, Callable, Set
|
||||
|
||||
@ -13,6 +12,7 @@ from bpy.app.handlers import persistent
|
||||
import lnx
|
||||
import lnx.api
|
||||
import lnx.nodes_logic
|
||||
import lnx.render_engine
|
||||
import lnx.make_state as state
|
||||
import lnx.utils
|
||||
import lnx.utils_vs
|
||||
@ -25,6 +25,7 @@ if lnx.is_reload(__name__):
|
||||
log = lnx.reload_module(log)
|
||||
lnx_nodes = lnx.reload_module(lnx_nodes)
|
||||
lnx.nodes_logic = lnx.reload_module(lnx.nodes_logic)
|
||||
lnx.render_engine = lnx.reload_module(lnx.render_engine)
|
||||
make = lnx.reload_module(make)
|
||||
state = lnx.reload_module(state)
|
||||
props = lnx.reload_module(props)
|
||||
@ -33,10 +34,8 @@ if lnx.is_reload(__name__):
|
||||
else:
|
||||
lnx.enable_reload(__name__)
|
||||
|
||||
# Module-level storage for active threads (eliminates re-queuing overhead)
|
||||
# Module-level storage for active threads
|
||||
_active_threads: Dict[threading.Thread, Callable] = {}
|
||||
_last_poll_time = 0.0
|
||||
_consecutive_empty_polls = 0
|
||||
_last_render_engine = None
|
||||
|
||||
@persistent
|
||||
@ -169,6 +168,14 @@ def check_render_engine() -> float:
|
||||
|
||||
elif _last_render_engine == 'KROM_VIEWPORT':
|
||||
try:
|
||||
for vid, engine in list(lnx.render_engine._active_krom_engines.items()):
|
||||
try:
|
||||
engine._restore_overlay()
|
||||
except:
|
||||
pass
|
||||
lnx.render_engine._active_krom_engines.clear()
|
||||
lnx.render_engine._active_krom_engine = None
|
||||
lnx.render_engine._active_viewport_id = None
|
||||
make.stop_viewport()
|
||||
except Exception as e:
|
||||
log.warn(f'Failed to stop viewport: {e}')
|
||||
@ -183,113 +190,31 @@ def check_render_engine() -> float:
|
||||
|
||||
|
||||
def poll_threads() -> float:
|
||||
"""
|
||||
Improved thread polling with:
|
||||
- No re-queuing overhead
|
||||
- Batch processing of completed threads
|
||||
- Adaptive timing based on activity
|
||||
- Better memory management
|
||||
- Simplified logic flow
|
||||
"""
|
||||
global _last_poll_time, _consecutive_empty_polls
|
||||
current_time = time.time()
|
||||
|
||||
# Process all new threads from queue at once (batch processing)
|
||||
new_threads_added = 0
|
||||
"""Polls the thread callback queue and processes completed threads."""
|
||||
# Drain queue into active threads
|
||||
try:
|
||||
while True:
|
||||
thread, callback = make.thread_callback_queue.get(block=False)
|
||||
_active_threads[thread] = callback
|
||||
new_threads_added += 1
|
||||
except queue.Empty:
|
||||
pass
|
||||
|
||||
# Early return if no active threads
|
||||
|
||||
if not _active_threads:
|
||||
_consecutive_empty_polls += 1
|
||||
# Adaptive timing: longer intervals when consistently empty
|
||||
if _consecutive_empty_polls > 10:
|
||||
return 0.5 # Back off when no activity
|
||||
return 0.25
|
||||
|
||||
# Reset empty poll counter when we have active threads
|
||||
_consecutive_empty_polls = 0
|
||||
|
||||
# Find completed threads (single pass, no re-queuing)
|
||||
completed_threads = []
|
||||
|
||||
# Join and callback all completed threads
|
||||
for thread in list(_active_threads.keys()):
|
||||
if not thread.is_alive():
|
||||
completed_threads.append(thread)
|
||||
|
||||
# Batch process all completed threads
|
||||
if completed_threads:
|
||||
_process_completed_threads(completed_threads)
|
||||
|
||||
# Adaptive timing based on activity level
|
||||
active_count = len(_active_threads)
|
||||
if active_count == 0:
|
||||
return 0.25
|
||||
elif active_count <= 3:
|
||||
return 0.05 # Medium frequency for low activity
|
||||
else:
|
||||
return 0.01 # High frequency for high activity
|
||||
callback = _active_threads.pop(thread)
|
||||
try:
|
||||
thread.join()
|
||||
callback()
|
||||
except Exception as e:
|
||||
bpy.app.timers.unregister(poll_threads)
|
||||
bpy.app.timers.register(poll_threads, first_interval=0.01, persistent=True)
|
||||
raise e
|
||||
|
||||
def _process_completed_threads(completed_threads: list) -> None:
|
||||
"""Process a batch of completed threads with robust error handling."""
|
||||
for thread in completed_threads:
|
||||
callback = _active_threads.pop(thread) # Remove from tracking
|
||||
|
||||
try:
|
||||
thread.join() # Should be instant since thread is dead
|
||||
callback()
|
||||
except Exception as e:
|
||||
# Robust error recovery
|
||||
_handle_callback_error(e)
|
||||
continue # Continue processing other threads
|
||||
|
||||
# Explicit cleanup for better memory management
|
||||
del thread, callback
|
||||
|
||||
def _handle_callback_error(exception: Exception) -> None:
|
||||
"""Centralized error handling with better recovery."""
|
||||
try:
|
||||
# Try to unregister existing timer
|
||||
bpy.app.timers.unregister(poll_threads)
|
||||
except ValueError:
|
||||
pass # Timer wasn't registered, that's fine
|
||||
|
||||
# Re-register timer with slightly longer interval for stability
|
||||
bpy.app.timers.register(poll_threads, first_interval=0.1, persistent=True)
|
||||
|
||||
# Re-raise the original exception after ensuring timer continuity
|
||||
raise exception
|
||||
|
||||
def cleanup_polling_system() -> None:
|
||||
"""Optional cleanup function for proper shutdown."""
|
||||
global _active_threads, _consecutive_empty_polls
|
||||
|
||||
# Wait for remaining threads to complete (with timeout)
|
||||
for thread in list(_active_threads.keys()):
|
||||
if thread.is_alive():
|
||||
thread.join(timeout=1.0) # 1 second timeout
|
||||
|
||||
# Clear tracking structures
|
||||
_active_threads.clear()
|
||||
_consecutive_empty_polls = 0
|
||||
|
||||
# Unregister timer
|
||||
try:
|
||||
bpy.app.timers.unregister(poll_threads)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def get_polling_stats() -> dict:
|
||||
"""Get statistics about the polling system for monitoring."""
|
||||
return {
|
||||
'active_threads': len(_active_threads),
|
||||
'consecutive_empty_polls': _consecutive_empty_polls,
|
||||
'thread_ids': [t.ident for t in _active_threads.keys()]
|
||||
}
|
||||
return 0.01
|
||||
|
||||
|
||||
loaded_py_libraries: Dict[str, types.ModuleType] = {}
|
||||
|
||||
Reference in New Issue
Block a user