#!/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()