mirror of
https://github.com/deepmodeling/Uni-Lab-OS
synced 2026-03-24 07:23:48 +00:00
fast registry load
This commit is contained in:
@@ -12,6 +12,7 @@ import io
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -102,6 +103,7 @@ class Registry:
|
|||||||
|
|
||||||
self.device_type_registry: Dict[str, Any] = {}
|
self.device_type_registry: Dict[str, Any] = {}
|
||||||
self.resource_type_registry: Dict[str, Any] = {}
|
self.resource_type_registry: Dict[str, Any] = {}
|
||||||
|
self._type_resolve_cache: Dict[str, Any] = {}
|
||||||
|
|
||||||
self._setup_called = False
|
self._setup_called = False
|
||||||
self._startup_executor: Optional[ThreadPoolExecutor] = None
|
self._startup_executor: Optional[ThreadPoolExecutor] = None
|
||||||
@@ -412,10 +414,20 @@ class Registry:
|
|||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
def _replace_type_with_class(self, type_name: str, device_id: str, field_name: str) -> Any:
|
def _replace_type_with_class(self, type_name: str, device_id: str, field_name: str) -> Any:
|
||||||
"""将类型名称替换为实际的 ROS 消息类对象"""
|
"""将类型名称替换为实际的 ROS 消息类对象(带缓存)"""
|
||||||
if not type_name or type_name == "":
|
if not type_name or type_name == "":
|
||||||
return type_name
|
return type_name
|
||||||
|
|
||||||
|
cached = self._type_resolve_cache.get(type_name)
|
||||||
|
if cached is not None:
|
||||||
|
return cached
|
||||||
|
|
||||||
|
result = self._resolve_type_uncached(type_name, device_id, field_name)
|
||||||
|
self._type_resolve_cache[type_name] = result
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _resolve_type_uncached(self, type_name: str, device_id: str, field_name: str) -> Any:
|
||||||
|
"""实际的类型解析逻辑(无缓存)"""
|
||||||
# 泛型类型映射
|
# 泛型类型映射
|
||||||
if "[" in type_name:
|
if "[" in type_name:
|
||||||
generic_mapping = {
|
generic_mapping = {
|
||||||
@@ -451,7 +463,6 @@ class Registry:
|
|||||||
if type_class:
|
if type_class:
|
||||||
return type_class
|
return type_class
|
||||||
else:
|
else:
|
||||||
# dataclass / TypedDict 等非 ROS2 类型,序列化为 JSON 字符串
|
|
||||||
logger.trace(
|
logger.trace(
|
||||||
f"[Registry] 类型 '{type_name}' 非 ROS2 消息类型 (设备 {device_id} {field_name}),映射为 String"
|
f"[Registry] 类型 '{type_name}' 非 ROS2 消息类型 (设备 {device_id} {field_name}),映射为 String"
|
||||||
)
|
)
|
||||||
@@ -1151,8 +1162,8 @@ class Registry:
|
|||||||
tmp = cache_path.with_suffix(".tmp")
|
tmp = cache_path.with_suffix(".tmp")
|
||||||
tmp.write_bytes(pickle.dumps(cache, protocol=pickle.HIGHEST_PROTOCOL))
|
tmp.write_bytes(pickle.dumps(cache, protocol=pickle.HIGHEST_PROTOCOL))
|
||||||
tmp.replace(cache_path)
|
tmp.replace(cache_path)
|
||||||
except Exception:
|
except Exception as e:
|
||||||
pass
|
logger.debug(f"[UniLab Registry] 缓存保存失败: {e}")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _module_source_hash(module_str: str) -> Optional[str]:
|
def _module_source_hash(module_str: str) -> Optional[str]:
|
||||||
@@ -1454,11 +1465,16 @@ class Registry:
|
|||||||
|
|
||||||
仅做 ROS2 消息类型查找,不 import 任何设备模块,速度快且无副作用。
|
仅做 ROS2 消息类型查找,不 import 任何设备模块,速度快且无副作用。
|
||||||
"""
|
"""
|
||||||
|
t0 = time.time()
|
||||||
for device_id in list(self.device_type_registry):
|
for device_id in list(self.device_type_registry):
|
||||||
try:
|
try:
|
||||||
self.resolve_types_for_device(device_id)
|
self.resolve_types_for_device(device_id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"[Registry] 设备 {device_id} 类型解析失败: {e}")
|
logger.debug(f"[Registry] 设备 {device_id} 类型解析失败: {e}")
|
||||||
|
logger.info(
|
||||||
|
f"[UniLab Registry] 类型解析完成: {len(self.device_type_registry)} 设备 "
|
||||||
|
f"(耗时 {time.time() - t0:.2f}s)"
|
||||||
|
)
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# YAML 注册表加载 (兼容旧格式)
|
# YAML 注册表加载 (兼容旧格式)
|
||||||
@@ -1946,7 +1962,31 @@ class Registry:
|
|||||||
|
|
||||||
return data, complete_data, True, device_ids
|
return data, complete_data, True, device_ids
|
||||||
|
|
||||||
|
def _rebuild_device_runtime_data(self, complete_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""从 complete_data(纯字符串)重建运行时数据(type 字段替换为 class 对象)。"""
|
||||||
|
data = copy.deepcopy(complete_data)
|
||||||
|
for device_id, device_config in data.items():
|
||||||
|
if "class" not in device_config:
|
||||||
|
continue
|
||||||
|
# status_types: str → class
|
||||||
|
for st_name, st_type in device_config["class"].get("status_types", {}).items():
|
||||||
|
if isinstance(st_type, str):
|
||||||
|
device_config["class"]["status_types"][st_name] = self._replace_type_with_class(
|
||||||
|
st_type, device_id, f"状态 {st_name}"
|
||||||
|
)
|
||||||
|
# action type: str → class (non-UniLabJsonCommand only)
|
||||||
|
for _act_name, act_cfg in device_config["class"].get("action_value_mappings", {}).items():
|
||||||
|
t_ref = act_cfg.get("type", "")
|
||||||
|
if isinstance(t_ref, str) and t_ref and not t_ref.startswith("UniLabJsonCommand"):
|
||||||
|
resolved = self._replace_type_with_class(t_ref, device_id, f"动作 {_act_name}")
|
||||||
|
if resolved:
|
||||||
|
act_cfg["type"] = resolved
|
||||||
|
self._add_builtin_actions(device_config, device_id)
|
||||||
|
return data
|
||||||
|
|
||||||
def load_device_types(self, path: os.PathLike, complete_registry: bool = False):
|
def load_device_types(self, path: os.PathLike, complete_registry: bool = False):
|
||||||
|
import hashlib as _hl
|
||||||
|
t0 = time.time()
|
||||||
abs_path = Path(path).absolute()
|
abs_path = Path(path).absolute()
|
||||||
devices_path = abs_path / "devices"
|
devices_path = abs_path / "devices"
|
||||||
device_comms_path = abs_path / "device_comms"
|
device_comms_path = abs_path / "device_comms"
|
||||||
@@ -1959,12 +1999,41 @@ class Registry:
|
|||||||
if not files:
|
if not files:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
config_cache = self._load_config_cache()
|
||||||
|
yaml_dev_cache: dict = config_cache.get("_yaml_devices", {})
|
||||||
|
cache_hits = 0
|
||||||
|
uncached_files: list[Path] = []
|
||||||
|
|
||||||
|
if complete_registry:
|
||||||
|
uncached_files = files
|
||||||
|
else:
|
||||||
|
for file in files:
|
||||||
|
file_key = str(file.absolute()).replace("\\", "/")
|
||||||
|
try:
|
||||||
|
yaml_md5 = _hl.md5(file.read_bytes()).hexdigest()
|
||||||
|
except OSError:
|
||||||
|
uncached_files.append(file)
|
||||||
|
continue
|
||||||
|
cached = yaml_dev_cache.get(file_key)
|
||||||
|
if cached and cached.get("yaml_md5") == yaml_md5 and cached.get("entries"):
|
||||||
|
complete_data = cached["entries"]
|
||||||
|
# 过滤掉 AST 已有的设备
|
||||||
|
complete_data = {
|
||||||
|
did: cfg for did, cfg in complete_data.items()
|
||||||
|
if not self.device_type_registry.get(did)
|
||||||
|
}
|
||||||
|
runtime_data = self._rebuild_device_runtime_data(complete_data)
|
||||||
|
self.device_type_registry.update(runtime_data)
|
||||||
|
cache_hits += 1
|
||||||
|
continue
|
||||||
|
uncached_files.append(file)
|
||||||
|
|
||||||
executor = self._startup_executor
|
executor = self._startup_executor
|
||||||
future_to_file = {
|
future_to_file = {
|
||||||
executor.submit(
|
executor.submit(
|
||||||
self._load_single_device_file, file, complete_registry
|
self._load_single_device_file, file, complete_registry
|
||||||
): file
|
): file
|
||||||
for file in files
|
for file in uncached_files
|
||||||
}
|
}
|
||||||
|
|
||||||
for future in as_completed(future_to_file):
|
for future in as_completed(future_to_file):
|
||||||
@@ -1974,9 +2043,33 @@ class Registry:
|
|||||||
if is_valid:
|
if is_valid:
|
||||||
runtime_data = {did: data[did] for did in device_ids if did in data}
|
runtime_data = {did: data[did] for did in device_ids if did in data}
|
||||||
self.device_type_registry.update(runtime_data)
|
self.device_type_registry.update(runtime_data)
|
||||||
|
# 写入缓存
|
||||||
|
file_key = str(file.absolute()).replace("\\", "/")
|
||||||
|
try:
|
||||||
|
yaml_md5 = _hl.md5(file.read_bytes()).hexdigest()
|
||||||
|
yaml_dev_cache[file_key] = {
|
||||||
|
"yaml_md5": yaml_md5,
|
||||||
|
"entries": _complete_data,
|
||||||
|
}
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"[UniLab Registry] 加载设备文件失败: {file}, 错误: {e}")
|
logger.warning(f"[UniLab Registry] 加载设备文件失败: {file}, 错误: {e}")
|
||||||
|
|
||||||
|
if uncached_files and yaml_dev_cache:
|
||||||
|
latest_cache = self._load_config_cache()
|
||||||
|
latest_cache["_yaml_devices"] = yaml_dev_cache
|
||||||
|
self._save_config_cache(latest_cache)
|
||||||
|
|
||||||
|
total = len(files)
|
||||||
|
extra = " (complete_registry 跳过缓存)" if complete_registry else ""
|
||||||
|
logger.info(
|
||||||
|
f"[UniLab Registry] YAML 设备加载: "
|
||||||
|
f"{cache_hits}/{total} 缓存命中, "
|
||||||
|
f"{len(uncached_files)} 重新加载 "
|
||||||
|
f"(耗时 {time.time() - t0:.2f}s){extra}"
|
||||||
|
)
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# 注册表信息输出
|
# 注册表信息输出
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ class ImportManager:
|
|||||||
self._modules: Dict[str, Any] = {}
|
self._modules: Dict[str, Any] = {}
|
||||||
self._classes: Dict[str, Type] = {}
|
self._classes: Dict[str, Type] = {}
|
||||||
self._functions: Dict[str, Callable] = {}
|
self._functions: Dict[str, Callable] = {}
|
||||||
|
self._search_miss: set = set()
|
||||||
|
|
||||||
if module_list:
|
if module_list:
|
||||||
for module_path in module_list:
|
for module_path in module_list:
|
||||||
@@ -155,29 +156,30 @@ class ImportManager:
|
|||||||
Returns:
|
Returns:
|
||||||
找到的类对象,如果未找到则返回None
|
找到的类对象,如果未找到则返回None
|
||||||
"""
|
"""
|
||||||
# 如果cls_name是builtins中的关键字,则返回对应类
|
|
||||||
if class_name in builtins.__dict__:
|
if class_name in builtins.__dict__:
|
||||||
return builtins.__dict__[class_name]
|
return builtins.__dict__[class_name]
|
||||||
# 首先在已索引的类中查找
|
|
||||||
if class_name in self._classes:
|
if class_name in self._classes:
|
||||||
return self._classes[class_name]
|
return self._classes[class_name]
|
||||||
|
|
||||||
|
cache_key = class_name.lower() if search_lower else class_name
|
||||||
|
if cache_key in self._search_miss:
|
||||||
|
return None
|
||||||
|
|
||||||
if search_lower:
|
if search_lower:
|
||||||
classes = {name.lower(): obj for name, obj in self._classes.items()}
|
classes = {name.lower(): obj for name, obj in self._classes.items()}
|
||||||
if class_name in classes:
|
if class_name in classes:
|
||||||
return classes[class_name]
|
return classes[class_name]
|
||||||
|
|
||||||
# 遍历所有已加载的模块进行搜索
|
|
||||||
for module_path, module in self._modules.items():
|
for module_path, module in self._modules.items():
|
||||||
for name, obj in inspect.getmembers(module):
|
for name, obj in inspect.getmembers(module):
|
||||||
if inspect.isclass(obj) and (
|
if inspect.isclass(obj) and (
|
||||||
(name.lower() == class_name.lower()) if search_lower else (name == class_name)
|
(name.lower() == class_name.lower()) if search_lower else (name == class_name)
|
||||||
):
|
):
|
||||||
# 将找到的类添加到索引中
|
|
||||||
self._classes[name] = obj
|
self._classes[name] = obj
|
||||||
self._classes[f"{module_path}:{name}"] = obj
|
self._classes[f"{module_path}:{name}"] = obj
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
self._search_miss.add(cache_key)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_enhanced_class_info(self, module_path: str, **_kwargs) -> Dict[str, Any]:
|
def get_enhanced_class_info(self, module_path: str, **_kwargs) -> Dict[str, Any]:
|
||||||
|
|||||||
Reference in New Issue
Block a user