583 lines
22 KiB
Python
583 lines
22 KiB
Python
|
#!/usr/bin/env python3
|
|||
|
# -*- coding: utf-8 -*-
|
|||
|
"""
|
|||
|
SUW Core - Memory Manager Module
|
|||
|
拆分自: suw_impl.py (Line 82-605)
|
|||
|
用途: Blender内存管理、依赖图管理、主线程处理
|
|||
|
版本: 1.0.0
|
|||
|
作者: SUWood Team
|
|||
|
"""
|
|||
|
|
|||
|
import time
|
|||
|
import logging
|
|||
|
import threading
|
|||
|
import queue
|
|||
|
from typing import Dict, Callable
|
|||
|
from contextlib import contextmanager
|
|||
|
|
|||
|
# 设置日志
|
|||
|
logger = logging.getLogger(__name__)
|
|||
|
|
|||
|
# 检查Blender可用性
|
|||
|
try:
|
|||
|
import bpy
|
|||
|
BLENDER_AVAILABLE = True
|
|||
|
except ImportError:
|
|||
|
BLENDER_AVAILABLE = False
|
|||
|
|
|||
|
# 全局主线程任务队列
|
|||
|
_main_thread_queue = queue.Queue()
|
|||
|
_main_thread_id = None
|
|||
|
|
|||
|
# ==================== 内存管理核心类 ====================
|
|||
|
|
|||
|
|
|||
|
class BlenderMemoryManager:
|
|||
|
"""Blender内存管理器 - 修复弱引用问题"""
|
|||
|
|
|||
|
def __init__(self):
|
|||
|
# 改用普通集合和字典来跟踪对象,而不是弱引用
|
|||
|
self.tracked_objects = set() # 存储对象名称而不是对象本身
|
|||
|
self.tracked_meshes = set() # 存储网格名称
|
|||
|
self.tracked_images = set() # 存储图像名称
|
|||
|
self.tracked_materials = set() # 存储材质名称
|
|||
|
self.tracked_collections = set() # 存储集合名称
|
|||
|
self.cleanup_interval = 100
|
|||
|
self.operation_count = 0
|
|||
|
self.last_cleanup = time.time()
|
|||
|
self.max_memory_mb = 2048
|
|||
|
self._cleanup_lock = threading.Lock()
|
|||
|
|
|||
|
self.creation_stats = {
|
|||
|
"objects_created": 0,
|
|||
|
"objects_cleaned": 0
|
|||
|
}
|
|||
|
|
|||
|
def register_object(self, obj):
|
|||
|
"""注册对象到内存管理器 - 修复版本"""
|
|||
|
if obj is None or not BLENDER_AVAILABLE:
|
|||
|
return
|
|||
|
|
|||
|
try:
|
|||
|
with self._cleanup_lock:
|
|||
|
# 根据对象类型分别处理
|
|||
|
if hasattr(obj, 'name'):
|
|||
|
obj_name = obj.name
|
|||
|
|
|||
|
# 根据对象类型存储到不同的集合
|
|||
|
if hasattr(obj, 'type'): # Blender Object
|
|||
|
self.tracked_objects.add(obj_name)
|
|||
|
elif str(type(obj)).find('Material') != -1: # Material
|
|||
|
self.tracked_materials.add(obj_name)
|
|||
|
elif str(type(obj)).find('Mesh') != -1: # Mesh
|
|||
|
self.tracked_meshes.add(obj_name)
|
|||
|
elif str(type(obj)).find('Image') != -1: # Image
|
|||
|
self.tracked_images.add(obj_name)
|
|||
|
elif str(type(obj)).find('Collection') != -1: # Collection
|
|||
|
self.tracked_collections.add(obj_name)
|
|||
|
else:
|
|||
|
self.tracked_objects.add(obj_name)
|
|||
|
|
|||
|
self.operation_count += 1
|
|||
|
|
|||
|
# 定期清理
|
|||
|
if self.should_cleanup():
|
|||
|
self.cleanup_orphaned_data()
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
# 静默处理,不输出错误日志
|
|||
|
pass
|
|||
|
|
|||
|
def register_mesh(self, mesh):
|
|||
|
"""注册网格到内存管理器 - 修复版本"""
|
|||
|
if mesh is None or not BLENDER_AVAILABLE:
|
|||
|
return
|
|||
|
|
|||
|
try:
|
|||
|
with self._cleanup_lock:
|
|||
|
if hasattr(mesh, 'name'):
|
|||
|
self.tracked_meshes.add(mesh.name)
|
|||
|
self.operation_count += 1
|
|||
|
except Exception as e:
|
|||
|
# 静默处理
|
|||
|
pass
|
|||
|
|
|||
|
def register_image(self, image):
|
|||
|
"""注册图像到内存管理器 - 修复版本"""
|
|||
|
if image is None or not BLENDER_AVAILABLE:
|
|||
|
return
|
|||
|
|
|||
|
try:
|
|||
|
with self._cleanup_lock:
|
|||
|
if hasattr(image, 'name'):
|
|||
|
self.tracked_images.add(image.name)
|
|||
|
self.operation_count += 1
|
|||
|
except Exception as e:
|
|||
|
# 静默处理
|
|||
|
pass
|
|||
|
|
|||
|
def should_cleanup(self):
|
|||
|
"""检查是否需要清理"""
|
|||
|
return (self.operation_count >= self.cleanup_interval or
|
|||
|
time.time() - self.last_cleanup > 300) # 5分钟强制清理
|
|||
|
|
|||
|
def cleanup_orphaned_data(self):
|
|||
|
"""【暂时禁用】清理孤立的数据块 - 让Blender自动处理以避免冲突"""
|
|||
|
if not BLENDER_AVAILABLE:
|
|||
|
return
|
|||
|
|
|||
|
# 【临时策略】完全禁用自动清理,只更新跟踪状态
|
|||
|
logger.debug("🚫 自动清理已禁用,让Blender自动处理孤立数据")
|
|||
|
|
|||
|
cleanup_count = 0
|
|||
|
|
|||
|
try:
|
|||
|
with self._cleanup_lock:
|
|||
|
# 只清理跟踪列表,不实际删除任何数据
|
|||
|
invalid_objects = []
|
|||
|
invalid_meshes = []
|
|||
|
invalid_materials = []
|
|||
|
invalid_images = []
|
|||
|
|
|||
|
# 清理无效的对象引用(不删除实际对象)
|
|||
|
for obj_name in list(self.tracked_objects):
|
|||
|
try:
|
|||
|
if obj_name not in bpy.data.objects:
|
|||
|
invalid_objects.append(obj_name)
|
|||
|
except:
|
|||
|
invalid_objects.append(obj_name)
|
|||
|
|
|||
|
# 清理无效的网格引用(不删除实际网格)
|
|||
|
for mesh_name in list(self.tracked_meshes):
|
|||
|
try:
|
|||
|
if mesh_name not in bpy.data.meshes:
|
|||
|
invalid_meshes.append(mesh_name)
|
|||
|
except:
|
|||
|
invalid_meshes.append(mesh_name)
|
|||
|
|
|||
|
# 清理无效的材质引用(不删除实际材质)
|
|||
|
for mat_name in list(self.tracked_materials):
|
|||
|
try:
|
|||
|
if mat_name not in bpy.data.materials:
|
|||
|
invalid_materials.append(mat_name)
|
|||
|
except:
|
|||
|
invalid_materials.append(mat_name)
|
|||
|
|
|||
|
# 清理无效的图像引用(不删除实际图像)
|
|||
|
for img_name in list(self.tracked_images):
|
|||
|
try:
|
|||
|
if img_name not in bpy.data.images:
|
|||
|
invalid_images.append(img_name)
|
|||
|
except:
|
|||
|
invalid_images.append(img_name)
|
|||
|
|
|||
|
# 只更新跟踪列表,不删除实际数据
|
|||
|
for obj_name in invalid_objects:
|
|||
|
self.tracked_objects.discard(obj_name)
|
|||
|
for mesh_name in invalid_meshes:
|
|||
|
self.tracked_meshes.discard(mesh_name)
|
|||
|
for mat_name in invalid_materials:
|
|||
|
self.tracked_materials.discard(mat_name)
|
|||
|
for img_name in invalid_images:
|
|||
|
self.tracked_images.discard(img_name)
|
|||
|
|
|||
|
total_cleaned = len(invalid_objects) + len(invalid_meshes) + \
|
|||
|
len(invalid_materials) + len(invalid_images)
|
|||
|
if total_cleaned > 0:
|
|||
|
logger.debug(f"🧹 清理了 {total_cleaned} 个无效引用(不删除实际数据)")
|
|||
|
|
|||
|
# 【修复】安全清理材质数据
|
|||
|
materials_to_remove = []
|
|||
|
for material_name in list(self.tracked_materials):
|
|||
|
try:
|
|||
|
if material_name in bpy.data.materials:
|
|||
|
material = bpy.data.materials[material_name]
|
|||
|
if material.users == 0:
|
|||
|
materials_to_remove.append(material_name)
|
|||
|
else:
|
|||
|
self.tracked_materials.discard(material_name)
|
|||
|
except Exception as e:
|
|||
|
logger.warning(f"检查材质 {material_name} 时出错: {e}")
|
|||
|
self.tracked_materials.discard(material_name)
|
|||
|
|
|||
|
# 批量删除无用的材质
|
|||
|
for material_name in materials_to_remove:
|
|||
|
try:
|
|||
|
if material_name in bpy.data.materials:
|
|||
|
material = bpy.data.materials[material_name]
|
|||
|
bpy.data.materials.remove(material, do_unlink=True)
|
|||
|
cleanup_count += 1
|
|||
|
self.tracked_materials.discard(material_name)
|
|||
|
except Exception as e:
|
|||
|
logger.warning(f"删除材质数据失败: {e}")
|
|||
|
self.tracked_materials.discard(material_name)
|
|||
|
|
|||
|
# 【修复】安全清理图像数据
|
|||
|
images_to_remove = []
|
|||
|
for image_name in list(self.tracked_images):
|
|||
|
try:
|
|||
|
if image_name in bpy.data.images:
|
|||
|
image = bpy.data.images[image_name]
|
|||
|
if image.users == 0:
|
|||
|
images_to_remove.append(image_name)
|
|||
|
else:
|
|||
|
self.tracked_images.discard(image_name)
|
|||
|
except Exception as e:
|
|||
|
logger.warning(f"检查图像 {image_name} 时出错: {e}")
|
|||
|
self.tracked_images.discard(image_name)
|
|||
|
|
|||
|
# 批量删除无用的图像
|
|||
|
for image_name in images_to_remove:
|
|||
|
try:
|
|||
|
if image_name in bpy.data.images:
|
|||
|
image = bpy.data.images[image_name]
|
|||
|
bpy.data.images.remove(image, do_unlink=True)
|
|||
|
cleanup_count += 1
|
|||
|
self.tracked_images.discard(image_name)
|
|||
|
except Exception as e:
|
|||
|
logger.warning(f"删除图像数据失败: {e}")
|
|||
|
self.tracked_images.discard(image_name)
|
|||
|
|
|||
|
# 【修复】清理无效的对象引用
|
|||
|
invalid_objects = []
|
|||
|
for obj_name in list(self.tracked_objects):
|
|||
|
try:
|
|||
|
if obj_name not in bpy.data.objects:
|
|||
|
invalid_objects.append(obj_name)
|
|||
|
except Exception as e:
|
|||
|
logger.warning(f"检查对象 {obj_name} 时出错: {e}")
|
|||
|
invalid_objects.append(obj_name)
|
|||
|
|
|||
|
for obj_name in invalid_objects:
|
|||
|
self.tracked_objects.discard(obj_name)
|
|||
|
|
|||
|
if cleanup_count > 0:
|
|||
|
logger.info(f"🧹 清理了 {cleanup_count} 个孤立数据块")
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"内存清理过程中发生错误: {e}")
|
|||
|
import traceback
|
|||
|
traceback.print_exc()
|
|||
|
|
|||
|
def _cleanup_tracked_references(self):
|
|||
|
"""清理跟踪集合中的无效引用"""
|
|||
|
try:
|
|||
|
# 清理无效的对象引用
|
|||
|
valid_objects = set()
|
|||
|
for obj_name in self.tracked_objects:
|
|||
|
if obj_name in bpy.data.objects:
|
|||
|
valid_objects.add(obj_name)
|
|||
|
self.tracked_objects = valid_objects
|
|||
|
|
|||
|
# 清理无效的网格引用
|
|||
|
valid_meshes = set()
|
|||
|
for mesh_name in self.tracked_meshes:
|
|||
|
if mesh_name in bpy.data.meshes:
|
|||
|
valid_meshes.add(mesh_name)
|
|||
|
self.tracked_meshes = valid_meshes
|
|||
|
|
|||
|
# 清理无效的材质引用
|
|||
|
valid_materials = set()
|
|||
|
for mat_name in self.tracked_materials:
|
|||
|
if mat_name in bpy.data.materials:
|
|||
|
valid_materials.add(mat_name)
|
|||
|
self.tracked_materials = valid_materials
|
|||
|
|
|||
|
# 清理无效的图像引用
|
|||
|
valid_images = set()
|
|||
|
for img_name in self.tracked_images:
|
|||
|
if img_name in bpy.data.images:
|
|||
|
valid_images.add(img_name)
|
|||
|
self.tracked_images = valid_images
|
|||
|
|
|||
|
# 清理无效的集合引用
|
|||
|
valid_collections = set()
|
|||
|
if hasattr(bpy.data, 'collections'):
|
|||
|
for col_name in self.tracked_collections:
|
|||
|
if col_name in bpy.data.collections:
|
|||
|
valid_collections.add(col_name)
|
|||
|
self.tracked_collections = valid_collections
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.warning(f"清理跟踪引用失败: {e}")
|
|||
|
|
|||
|
def get_memory_stats(self) -> Dict[str, int]:
|
|||
|
"""获取内存使用统计"""
|
|||
|
try:
|
|||
|
stats = {
|
|||
|
"manager_type": "BlenderMemoryManager", # 添加这个字段
|
|||
|
"tracked_objects": len(self.tracked_objects),
|
|||
|
"tracked_meshes": len(self.tracked_meshes),
|
|||
|
"tracked_images": len(self.tracked_images),
|
|||
|
"creation_count": self.creation_stats.get("objects_created", 0),
|
|||
|
"cleanup_count": self.creation_stats.get("objects_cleaned", 0),
|
|||
|
"blender_available": BLENDER_AVAILABLE
|
|||
|
}
|
|||
|
return stats
|
|||
|
except Exception as e:
|
|||
|
return {"manager_type": "BlenderMemoryManager", "error": str(e)}
|
|||
|
|
|||
|
def force_cleanup(self):
|
|||
|
"""强制清理"""
|
|||
|
try:
|
|||
|
with self._cleanup_lock:
|
|||
|
self.last_cleanup = 0 # 重置时间以强制清理
|
|||
|
self.cleanup_orphaned_data()
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"强制清理失败: {e}")
|
|||
|
|
|||
|
|
|||
|
# ==================== 依赖图管理器 ====================
|
|||
|
|
|||
|
class DependencyGraphManager:
|
|||
|
"""依赖图管理器 - 控制更新频率,避免过度更新导致的冲突"""
|
|||
|
|
|||
|
def __init__(self):
|
|||
|
self.update_interval = 0.1 # 100毫秒最小更新间隔
|
|||
|
self.last_update_time = 0
|
|||
|
self.pending_updates = False
|
|||
|
self._update_lock = threading.Lock()
|
|||
|
self._updating = False # 【新增】防止递归更新的标志
|
|||
|
|
|||
|
def request_update(self, force=False):
|
|||
|
"""请求依赖图更新 - 线程安全版本"""
|
|||
|
if not BLENDER_AVAILABLE:
|
|||
|
return
|
|||
|
|
|||
|
# 【新增】线程安全检查 - 只在主线程中执行更新
|
|||
|
if threading.current_thread().ident != _main_thread_id:
|
|||
|
logger.debug("跳过非主线程的依赖图更新")
|
|||
|
self.pending_updates = True
|
|||
|
return
|
|||
|
|
|||
|
with self._update_lock:
|
|||
|
current_time = time.time()
|
|||
|
|
|||
|
if force or (current_time - self.last_update_time) >= self.update_interval:
|
|||
|
try:
|
|||
|
# 【修复依赖图冲突】添加求值状态检查
|
|||
|
if hasattr(bpy.context, 'evaluated_depsgraph_get'):
|
|||
|
# 检查是否在依赖图求值过程中
|
|||
|
try:
|
|||
|
depsgraph = bpy.context.evaluated_depsgraph_get()
|
|||
|
if depsgraph.is_evaluating:
|
|||
|
logger.debug("⚠️ 依赖图正在求值中,跳过更新")
|
|||
|
return
|
|||
|
except:
|
|||
|
pass
|
|||
|
|
|||
|
# 【修复】使用延迟更新机制,避免递归调用
|
|||
|
if not getattr(self, '_updating', False):
|
|||
|
self._updating = True
|
|||
|
try:
|
|||
|
bpy.context.view_layer.update()
|
|||
|
self.last_update_time = current_time
|
|||
|
self.pending_updates = False
|
|||
|
logger.debug("✅ 依赖图更新完成")
|
|||
|
finally:
|
|||
|
self._updating = False
|
|||
|
else:
|
|||
|
logger.debug("⚠️ 依赖图更新正在进行中,跳过")
|
|||
|
|
|||
|
except (AttributeError, ReferenceError, RuntimeError) as e:
|
|||
|
# 这些错误在对象删除过程中是预期的
|
|||
|
logger.debug(f"依赖图更新时的预期错误: {e}")
|
|||
|
except Exception as e:
|
|||
|
logger.warning(f"依赖图更新失败: {e}")
|
|||
|
# 【新增】记录失败但不抛出异常
|
|||
|
if hasattr(self, '_updating'):
|
|||
|
self._updating = False
|
|||
|
|
|||
|
def flush_pending_updates(self):
|
|||
|
"""强制执行所有挂起的更新"""
|
|||
|
if self.pending_updates:
|
|||
|
self.request_update(force=True)
|
|||
|
|
|||
|
|
|||
|
# ==================== 主线程处理 ====================
|
|||
|
|
|||
|
def init_main_thread():
|
|||
|
"""初始化主线程ID"""
|
|||
|
global _main_thread_id
|
|||
|
_main_thread_id = threading.current_thread().ident
|
|||
|
|
|||
|
|
|||
|
def execute_in_main_thread_async(func: Callable, *args, **kwargs):
|
|||
|
"""
|
|||
|
【真正的异步版】在主线程中安全地调度函数 - 真正的"即发即忘",不等待结果。
|
|||
|
"""
|
|||
|
global _main_thread_queue, _main_thread_id
|
|||
|
|
|||
|
# 如果已经在主线程中,直接执行
|
|||
|
if threading.current_thread().ident == _main_thread_id:
|
|||
|
try:
|
|||
|
func(*args, **kwargs)
|
|||
|
return True
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"在主线程直接执行函数时出错: {e}")
|
|||
|
import traceback
|
|||
|
traceback.print_exc()
|
|||
|
return False
|
|||
|
|
|||
|
# 在Blender中,使用应用程序定时器 - 即发即忘模式
|
|||
|
try:
|
|||
|
import bpy
|
|||
|
|
|||
|
def timer_task():
|
|||
|
try:
|
|||
|
func(*args, **kwargs)
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"主线程任务执行失败: {e}")
|
|||
|
import traceback
|
|||
|
traceback.print_exc()
|
|||
|
return None # 只执行一次
|
|||
|
|
|||
|
# 注册定时器任务就立即返回,不等待结果
|
|||
|
bpy.app.timers.register(timer_task, first_interval=0.001)
|
|||
|
|
|||
|
# !!!关键:立即返回调度成功,不等待执行结果!!!
|
|||
|
return True
|
|||
|
|
|||
|
except ImportError:
|
|||
|
# 不在Blender环境中,使用原有的队列机制 - 也改为即发即忘
|
|||
|
def wrapper():
|
|||
|
try:
|
|||
|
func(*args, **kwargs)
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"队列任务执行失败: {e}")
|
|||
|
import traceback
|
|||
|
traceback.print_exc()
|
|||
|
|
|||
|
_main_thread_queue.put(wrapper)
|
|||
|
# 立即返回调度成功,不等待执行结果
|
|||
|
return True
|
|||
|
|
|||
|
|
|||
|
# 【保持向后兼容】旧函数名的别名
|
|||
|
execute_in_main_thread = execute_in_main_thread_async
|
|||
|
|
|||
|
|
|||
|
def process_main_thread_tasks():
|
|||
|
"""
|
|||
|
【修复版】处理主线程任务队列 - 一次只处理一个任务!
|
|||
|
这个函数需要被Blender的定时器定期调用。
|
|||
|
"""
|
|||
|
global _main_thread_queue
|
|||
|
|
|||
|
try:
|
|||
|
# !!!关键修改:从 while 改为 if !!!
|
|||
|
# 一次定时器触发,只处理队列中的一个任务,然后就把控制权还给Blender。
|
|||
|
if not _main_thread_queue.empty():
|
|||
|
task = _main_thread_queue.get_nowait()
|
|||
|
try:
|
|||
|
task()
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"执行主线程任务时出错: {e}")
|
|||
|
import traceback
|
|||
|
traceback.print_exc()
|
|||
|
except queue.Empty:
|
|||
|
pass # 队列是空的,什么也不做
|
|||
|
|
|||
|
|
|||
|
@contextmanager
|
|||
|
def safe_blender_operation(operation_name: str):
|
|||
|
"""线程安全的Blender操作上下文管理器 - 修复版本"""
|
|||
|
if not BLENDER_AVAILABLE:
|
|||
|
logger.warning(f"Blender不可用,跳过操作: {operation_name}")
|
|||
|
yield
|
|||
|
return
|
|||
|
|
|||
|
start_time = time.time()
|
|||
|
logger.debug(f"🔄 开始操作: {operation_name}")
|
|||
|
|
|||
|
# 保存当前状态
|
|||
|
original_mode = None
|
|||
|
original_selection = []
|
|||
|
original_active = None
|
|||
|
|
|||
|
def _execute_operation():
|
|||
|
nonlocal original_mode, original_selection, original_active
|
|||
|
|
|||
|
try:
|
|||
|
# 确保在对象模式下
|
|||
|
if hasattr(bpy.context, 'mode') and bpy.context.mode != 'OBJECT':
|
|||
|
original_mode = bpy.context.mode
|
|||
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|||
|
|
|||
|
# 保存当前选择和活动对象
|
|||
|
if hasattr(bpy.context, 'selected_objects'):
|
|||
|
original_selection = list(bpy.context.selected_objects)
|
|||
|
if hasattr(bpy.context, 'active_object'):
|
|||
|
original_active = bpy.context.active_object
|
|||
|
|
|||
|
# 清除选择以避免冲突
|
|||
|
bpy.ops.object.select_all(action='DESELECT')
|
|||
|
|
|||
|
return True
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"准备操作失败: {e}")
|
|||
|
return False
|
|||
|
|
|||
|
def _cleanup_operation():
|
|||
|
try:
|
|||
|
# 尝试恢复原始状态
|
|||
|
bpy.ops.object.select_all(action='DESELECT')
|
|||
|
for obj in original_selection:
|
|||
|
if obj and obj.name in bpy.data.objects:
|
|||
|
obj.select_set(True)
|
|||
|
|
|||
|
# 恢复活动对象
|
|||
|
if original_active and original_active.name in bpy.data.objects:
|
|||
|
bpy.context.view_layer.objects.active = original_active
|
|||
|
|
|||
|
# 恢复模式
|
|||
|
if original_mode and original_mode != 'OBJECT':
|
|||
|
bpy.ops.object.mode_set(mode=original_mode)
|
|||
|
|
|||
|
except Exception as restore_error:
|
|||
|
logger.warning(f"恢复状态失败: {restore_error}")
|
|||
|
|
|||
|
try:
|
|||
|
# 如果不在主线程,使用主线程执行准备操作
|
|||
|
if threading.current_thread().ident != _main_thread_id:
|
|||
|
success = execute_in_main_thread(_execute_operation)
|
|||
|
if not success:
|
|||
|
raise RuntimeError("准备操作失败")
|
|||
|
else:
|
|||
|
success = _execute_operation()
|
|||
|
if not success:
|
|||
|
raise RuntimeError("准备操作失败")
|
|||
|
|
|||
|
# 执行用户操作
|
|||
|
yield
|
|||
|
|
|||
|
elapsed_time = time.time() - start_time
|
|||
|
if elapsed_time > 5.0:
|
|||
|
logger.warning(f"操作耗时过长: {operation_name} ({elapsed_time:.2f}s)")
|
|||
|
else:
|
|||
|
logger.debug(f"✅ 操作完成: {operation_name} ({elapsed_time:.2f}s)")
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"❌ 操作失败: {operation_name} - {e}")
|
|||
|
raise
|
|||
|
|
|||
|
finally:
|
|||
|
# 清理操作也需要在主线程中执行
|
|||
|
if threading.current_thread().ident != _main_thread_id:
|
|||
|
try:
|
|||
|
execute_in_main_thread(_cleanup_operation)
|
|||
|
except:
|
|||
|
pass
|
|||
|
else:
|
|||
|
_cleanup_operation()
|
|||
|
|
|||
|
|
|||
|
# ==================== 全局实例 ====================
|
|||
|
|
|||
|
# 全局内存管理器实例
|
|||
|
memory_manager = BlenderMemoryManager()
|
|||
|
|
|||
|
# 全局依赖图管理器
|
|||
|
dependency_manager = DependencyGraphManager()
|