diff --git a/NavisworksTransportPlugin.csproj b/NavisworksTransportPlugin.csproj index ffa1060..680d9b3 100644 --- a/NavisworksTransportPlugin.csproj +++ b/NavisworksTransportPlugin.csproj @@ -163,6 +163,10 @@ + + + + diff --git a/src/PathPlanning/AutoPathFinder.cs b/src/PathPlanning/AutoPathFinder.cs index 033026f..2a31e6b 100644 --- a/src/PathPlanning/AutoPathFinder.cs +++ b/src/PathPlanning/AutoPathFinder.cs @@ -6,7 +6,6 @@ using Roy_T.AStar.Grids; using Roy_T.AStar.Primitives; using Roy_T.AStar.Paths; using NavisworksTransport.Utils; -using NavisworksTransport.Core; namespace NavisworksTransport.PathPlanning { @@ -117,6 +116,9 @@ namespace NavisworksTransport.PathPlanning // 路径优化 - 去除共线点 pathResult = OptimizePath(pathResult, gridMap); + // 记录缓存统计信息 + LogCacheStatistics(); + return pathResult; } catch (Exception ex) @@ -1214,8 +1216,6 @@ namespace NavisworksTransport.PathPlanning int gridX = gridPos.X; int gridY = gridPos.Y; - LogManager.Info($"[高度检查] 检查点 ({point.X:F2}, {point.Y:F2}, {point.Z:F2}) -> 网格坐标 ({gridX}, {gridY})"); - // 确保坐标在有效范围内 if (gridX < 0 || gridX >= gridMap.Width || gridY < 0 || gridY >= gridMap.Height) { @@ -1240,7 +1240,6 @@ namespace NavisworksTransport.PathPlanning // 将绝对坐标转换为相对于网格底面的坐标 double relativeZ = point.Z - cell.WorldPosition.Z; bool containsHeight = interval.Contains(relativeZ); - LogManager.Info($"[高度检查] 区间 {interval}: 跨度={interval.GetSpan():F2}模型单位 (需要≥{vehicleHeight:F2}模型单位): {spanOk}, 包含相对Z={relativeZ:F2}: {containsHeight}"); if (spanOk && containsHeight) { @@ -1433,10 +1432,6 @@ namespace NavisworksTransport.PathPlanning doorPoints++; LogManager.Info($"[路径高度验证] 检查路径点{i}(门网格): ({point.X:F2}, {point.Y:F2}, {point.Z:F2}) -> 网格({gridPos.X},{gridPos.Y})"); } - else - { - LogManager.Info($"[路径高度验证] 检查路径点{i}({cell.CellType}): ({point.X:F2}, {point.Y:F2}, {point.Z:F2}) -> 网格({gridPos.X},{gridPos.Y})"); - } // 进行高度约束检查 bool isPassable = IsPointPassableAtHeight(point, gridMap, vehicleHeight); @@ -1879,5 +1874,43 @@ namespace NavisworksTransport.PathPlanning return finalSpeed; } + + /// + /// 获取GridMap缓存统计信息 + /// + /// 缓存统计报告 + public static string GetGridMapCacheStatistics() + { + return GridMapGenerator.GetCacheStatistics(); + } + + /// + /// 获取GridMap缓存详细统计报告 + /// + /// 详细统计报告 + public static string GetDetailedGridMapCacheReport() + { + return GridMapGenerator.GetDetailedCacheReport(); + } + + /// + /// 清除GridMap缓存 + /// + public static void ClearGridMapCache() + { + GridMapGenerator.ClearCache(); + } + + /// + /// 记录路径规划完成时的缓存统计 + /// + private void LogCacheStatistics() + { + var stats = GlobalGridMapCache.Instance.Statistics; + if (stats.HitCount + stats.MissCount > 0) + { + LogManager.Info($"[GridMap缓存统计] {stats.GenerateReport()}"); + } + } } } \ No newline at end of file diff --git a/src/PathPlanning/GridCache.cs b/src/PathPlanning/GridCache.cs new file mode 100644 index 0000000..c40b7ef --- /dev/null +++ b/src/PathPlanning/GridCache.cs @@ -0,0 +1,331 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using Roy_T.AStar.Grids; + +namespace NavisworksTransport.PathPlanning +{ + /// + /// 缓存项,包含网格和访问时间信息 + /// + public class GridCacheItem + { + /// + /// 缓存的A*网格 + /// + public Grid Grid { get; set; } + + /// + /// 最后访问时间 + /// + public DateTime LastAccessTime { get; set; } + + /// + /// 创建时间 + /// + public DateTime CreatedTime { get; set; } + + /// + /// 访问次数 + /// + public int AccessCount { get; set; } + + /// + /// 构造函数 + /// + public GridCacheItem(Grid grid) + { + Grid = grid ?? throw new ArgumentNullException(nameof(grid)); + LastAccessTime = DateTime.Now; + CreatedTime = DateTime.Now; + AccessCount = 1; + } + + /// + /// 记录访问 + /// + public void RecordAccess() + { + LastAccessTime = DateTime.Now; + AccessCount++; + } + } + + /// + /// 网格缓存统计信息 + /// + public class GridCacheStatistics + { + /// + /// 缓存命中次数 + /// + public long HitCount { get; set; } + + /// + /// 缓存未命中次数 + /// + public long MissCount { get; set; } + + /// + /// 当前缓存项数量 + /// + public int CurrentCount { get; set; } + + /// + /// 最大缓存项数量 + /// + public int MaxCapacity { get; set; } + + /// + /// 被驱逐的缓存项数量 + /// + public long EvictedCount { get; set; } + + /// + /// 缓存命中率 + /// + public double HitRate => HitCount + MissCount > 0 ? (double)HitCount / (HitCount + MissCount) : 0.0; + + /// + /// 缓存使用率 + /// + public double UsageRate => MaxCapacity > 0 ? (double)CurrentCount / MaxCapacity : 0.0; + + /// + /// 重置统计信息 + /// + public void Reset() + { + HitCount = 0; + MissCount = 0; + EvictedCount = 0; + } + + /// + /// 生成统计报告 + /// + public string GenerateReport() + { + return $"网格缓存统计 - 命中率: {HitRate:P2} ({HitCount}命中/{MissCount}未命中), " + + $"使用率: {UsageRate:P2} ({CurrentCount}/{MaxCapacity}), " + + $"驱逐次数: {EvictedCount}"; + } + } + + /// + /// 网格缓存管理器 + /// 使用LRU策略管理A*网格缓存,支持线程安全访问 + /// + public class GridCache + { + private readonly ConcurrentDictionary _cache; + private readonly object _cleanupLock = new object(); + private readonly int _maxCapacity; + private readonly GridCacheStatistics _statistics; + + /// + /// 获取缓存统计信息 + /// + public GridCacheStatistics Statistics => _statistics; + + /// + /// 构造函数 + /// + /// 最大缓存容量,默认20个网格 + public GridCache(int maxCapacity = 20) + { + if (maxCapacity <= 0) + throw new ArgumentException("缓存容量必须大于0", nameof(maxCapacity)); + + _maxCapacity = maxCapacity; + _cache = new ConcurrentDictionary(); + _statistics = new GridCacheStatistics { MaxCapacity = maxCapacity }; + + LogManager.Info($"[网格缓存] 初始化完成,最大容量: {_maxCapacity}"); + } + + /// + /// 获取缓存的网格 + /// + /// 缓存键 + /// 网格对象,如果不存在则返回null + public Grid Get(GridCacheKey key) + { + if (key == null) + return null; + + if (_cache.TryGetValue(key, out var item)) + { + // 记录访问 + item.RecordAccess(); + _statistics.HitCount++; + + LogManager.Debug($"[网格缓存] 命中 - {key},访问次数: {item.AccessCount}"); + return item.Grid; + } + + _statistics.MissCount++; + LogManager.Debug($"[网格缓存] 未命中 - {key}"); + return null; + } + + /// + /// 添加网格到缓存 + /// + /// 缓存键 + /// A*网格 + public void Put(GridCacheKey key, Grid grid) + { + if (key == null || grid == null) + return; + + var item = new GridCacheItem(grid); + + // 添加或更新缓存项 + _cache.AddOrUpdate(key, item, (k, existing) => + { + LogManager.Debug($"[网格缓存] 更新现有项 - {key}"); + return item; + }); + + // 更新统计信息 + _statistics.CurrentCount = _cache.Count; + + LogManager.Debug($"[网格缓存] 添加 - {key},当前容量: {_cache.Count}/{_maxCapacity}"); + + // 检查是否需要清理 + if (_cache.Count > _maxCapacity) + { + CleanupCache(); + } + } + + /// + /// 检查缓存中是否包含指定键 + /// + /// 缓存键 + /// 是否包含 + public bool ContainsKey(GridCacheKey key) + { + return key != null && _cache.ContainsKey(key); + } + + /// + /// 清除所有缓存 + /// + public void Clear() + { + lock (_cleanupLock) + { + var count = _cache.Count; + _cache.Clear(); + _statistics.CurrentCount = 0; + _statistics.Reset(); + + LogManager.Info($"[网格缓存] 清除所有缓存,共清除 {count} 项"); + } + } + + /// + /// 获取缓存的所有键 + /// + /// 键列表 + public IList GetKeys() + { + return _cache.Keys.ToList(); + } + + /// + /// 清理缓存,使用LRU策略移除最少使用的项 + /// + private void CleanupCache() + { + lock (_cleanupLock) + { + // 双重检查,避免重复清理 + if (_cache.Count <= _maxCapacity) + return; + + var targetCount = (int)(_maxCapacity * 0.8); // 清理到80%容量 + var itemsToRemove = _cache.Count - targetCount; + + LogManager.Info($"[网格缓存] 开始LRU清理,当前: {_cache.Count},目标: {targetCount},需移除: {itemsToRemove}"); + + // 按最后访问时间排序,移除最久未访问的项 + var sortedItems = _cache + .OrderBy(kvp => kvp.Value.LastAccessTime) + .ThenBy(kvp => kvp.Value.AccessCount) // 相同时间的情况下,优先移除访问次数少的 + .Take(itemsToRemove) + .ToList(); + + int removedCount = 0; + foreach (var item in sortedItems) + { + if (_cache.TryRemove(item.Key, out _)) + { + removedCount++; + _statistics.EvictedCount++; + LogManager.Debug($"[网格缓存] 移除 - {item.Key},最后访问: {item.Value.LastAccessTime:yyyy-MM-dd HH:mm:ss}"); + } + } + + _statistics.CurrentCount = _cache.Count; + LogManager.Info($"[网格缓存] LRU清理完成,实际移除: {removedCount},当前容量: {_cache.Count}"); + } + } + + /// + /// 生成缓存详细报告 + /// + /// 报告字符串 + public string GenerateDetailedReport() + { + var report = new System.Text.StringBuilder(); + + report.AppendLine(_statistics.GenerateReport()); + + if (_cache.Count > 0) + { + report.AppendLine($"\n当前缓存项详情:"); + var sortedItems = _cache + .OrderByDescending(kvp => kvp.Value.LastAccessTime) + .Take(10) // 只显示最近10项 + .ToList(); + + foreach (var item in sortedItems) + { + report.AppendLine($" {item.Key} - 访问{item.Value.AccessCount}次,最后访问: {item.Value.LastAccessTime:MM-dd HH:mm:ss}"); + } + + if (_cache.Count > 10) + { + report.AppendLine($" ... 还有 {_cache.Count - 10} 项"); + } + } + + return report.ToString(); + } + } + + /// + /// 全局网格缓存管理器(单例) + /// + public static class GlobalGridCache + { + private static readonly Lazy _instance = new Lazy(() => new GridCache()); + + /// + /// 获取全局缓存实例 + /// + public static GridCache Instance => _instance.Value; + + /// + /// 重置全局缓存(主要用于测试) + /// + public static void Reset() + { + _instance.Value.Clear(); + } + } +} \ No newline at end of file diff --git a/src/PathPlanning/GridCacheKey.cs b/src/PathPlanning/GridCacheKey.cs new file mode 100644 index 0000000..bfcb542 --- /dev/null +++ b/src/PathPlanning/GridCacheKey.cs @@ -0,0 +1,185 @@ +using System; +using System.Security.Cryptography; +using System.Text; + +namespace NavisworksTransport.PathPlanning +{ + /// + /// 网格缓存键,用于标识唯一的网格配置 + /// 包含所有影响网格生成的因素:网格地图内容、车辆高度、路径策略、网格尺寸 + /// + public class GridCacheKey : IEquatable + { + /// + /// 网格地图内容哈希值 + /// + public string GridMapHash { get; set; } + + /// + /// 车辆高度(模型单位) + /// + public double VehicleHeight { get; set; } + + /// + /// 路径规划策略 + /// + public PathStrategy Strategy { get; set; } + + /// + /// 网格单元大小(模型单位) + /// + public double CellSize { get; set; } + + /// + /// 构造函数 + /// + /// 网格地图哈希 + /// 车辆高度 + /// 路径策略 + /// 网格尺寸 + public GridCacheKey(string gridMapHash, double vehicleHeight, PathStrategy strategy, double cellSize) + { + GridMapHash = gridMapHash ?? throw new ArgumentNullException(nameof(gridMapHash)); + VehicleHeight = vehicleHeight; + Strategy = strategy; + CellSize = cellSize; + } + + /// + /// 检查两个缓存键是否相等 + /// + public bool Equals(GridCacheKey other) + { + if (other == null) return false; + if (ReferenceEquals(this, other)) return true; + + return GridMapHash == other.GridMapHash && + Math.Abs(VehicleHeight - other.VehicleHeight) < 1e-6 && + Strategy == other.Strategy && + Math.Abs(CellSize - other.CellSize) < 1e-6; + } + + /// + /// 重写Equals方法 + /// + public override bool Equals(object obj) + { + return Equals(obj as GridCacheKey); + } + + /// + /// 计算哈希码,用于Dictionary键 + /// + public override int GetHashCode() + { + unchecked + { + int hash = 17; + hash = hash * 23 + (GridMapHash?.GetHashCode() ?? 0); + hash = hash * 23 + VehicleHeight.GetHashCode(); + hash = hash * 23 + Strategy.GetHashCode(); + hash = hash * 23 + CellSize.GetHashCode(); + return hash; + } + } + + /// + /// 生成可读的字符串表示 + /// + public override string ToString() + { + var hashPreview = GridMapHash != null ? + GridMapHash.Substring(0, Math.Min(8, GridMapHash.Length)) : + "NULL"; + return $"GridCacheKey[Hash={hashPreview}, " + + $"Height={VehicleHeight:F2}, Strategy={Strategy}, CellSize={CellSize:F2}]"; + } + + /// + /// 根据GridMap和参数生成缓存键 + /// + /// 网格地图 + /// 车辆高度 + /// 路径策略 + /// 缓存键 + public static GridCacheKey CreateFrom(GridMap gridMap, double vehicleHeight, PathStrategy strategy) + { + if (gridMap == null) + throw new ArgumentNullException(nameof(gridMap)); + + var gridMapHash = ComputeGridMapHash(gridMap); + return new GridCacheKey(gridMapHash, vehicleHeight, strategy, gridMap.CellSize); + } + + /// + /// 计算GridMap的内容哈希值 + /// 基于网格尺寸、单元格可通行性、高度约束等关键数据 + /// + /// 网格地图 + /// SHA256哈希字符串 + private static string ComputeGridMapHash(GridMap gridMap) + { + try + { + using (var sha256 = SHA256.Create()) + { + var hashBuilder = new StringBuilder(); + + // 基础属性 + hashBuilder.Append($"W:{gridMap.Width}|H:{gridMap.Height}|CS:{gridMap.CellSize:F6}|"); + hashBuilder.Append($"OX:{gridMap.Origin.X:F6}|OY:{gridMap.Origin.Y:F6}|OZ:{gridMap.Origin.Z:F6}|"); + + // 遍历所有网格单元 + for (int x = 0; x < gridMap.Width; x++) + { + for (int y = 0; y < gridMap.Height; y++) + { + var cell = gridMap.Cells[x, y]; + + // 基本属性 + hashBuilder.Append($"[{x},{y}]:{(cell.IsWalkable ? "1" : "0")}|{cell.CellType}|"); + + // 世界位置 + hashBuilder.Append($"WP:{cell.WorldPosition.X:F3},{cell.WorldPosition.Y:F3},{cell.WorldPosition.Z:F3}|"); + + // 高度约束区间 + if (cell.PassableHeights != null && cell.PassableHeights.Count > 0) + { + hashBuilder.Append("PH:"); + foreach (var interval in cell.PassableHeights) + { + hashBuilder.Append($"{interval.MinZ:F3}-{interval.MaxZ:F3},"); + } + hashBuilder.Append("|"); + } + else + { + hashBuilder.Append("PH:NONE|"); + } + } + } + + // 计算哈希 + var hashInput = hashBuilder.ToString(); + var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(hashInput)); + + // 转换为十六进制字符串 + var hexBuilder = new StringBuilder(); + foreach (byte b in hashBytes) + { + hexBuilder.Append(b.ToString("x2")); + } + + return hexBuilder.ToString(); + } + } + catch (Exception ex) + { + // 如果哈希计算失败,返回基于时间戳的唯一标识 + // 这样可以确保缓存不会错误命中,但会失去缓存效果 + LogManager.Warning($"[网格哈希] 计算网格哈希失败: {ex.Message},使用时间戳标识"); + return $"HASH_FAILED_{DateTime.Now.Ticks}"; + } + } + } +} \ No newline at end of file diff --git a/src/PathPlanning/GridMapCache.cs b/src/PathPlanning/GridMapCache.cs new file mode 100644 index 0000000..584fdc7 --- /dev/null +++ b/src/PathPlanning/GridMapCache.cs @@ -0,0 +1,347 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; + +namespace NavisworksTransport.PathPlanning +{ + /// + /// GridMap缓存项,包含网格地图和访问时间信息 + /// + public class GridMapCacheItem + { + /// + /// 缓存的网格地图 + /// + public GridMap GridMap { get; set; } + + /// + /// 最后访问时间 + /// + public DateTime LastAccessTime { get; set; } + + /// + /// 创建时间 + /// + public DateTime CreatedTime { get; set; } + + /// + /// 访问次数 + /// + public int AccessCount { get; set; } + + /// + /// 构造函数 + /// + public GridMapCacheItem(GridMap gridMap) + { + GridMap = gridMap ?? throw new ArgumentNullException(nameof(gridMap)); + LastAccessTime = DateTime.Now; + CreatedTime = DateTime.Now; + AccessCount = 1; + } + + /// + /// 记录访问 + /// + public void RecordAccess() + { + LastAccessTime = DateTime.Now; + AccessCount++; + } + } + + /// + /// GridMap缓存统计信息 + /// + public class GridMapCacheStatistics + { + /// + /// 缓存命中次数 + /// + public long HitCount { get; set; } + + /// + /// 缓存未命中次数 + /// + public long MissCount { get; set; } + + /// + /// 当前缓存项数量 + /// + public int CurrentCount { get; set; } + + /// + /// 最大缓存项数量 + /// + public int MaxCapacity { get; set; } + + /// + /// 被驱逐的缓存项数量 + /// + public long EvictedCount { get; set; } + + /// + /// 累计节省的构建时间(毫秒) + /// + public long TotalTimeSavedMs { get; set; } + + /// + /// 缓存命中率 + /// + public double HitRate => HitCount + MissCount > 0 ? (double)HitCount / (HitCount + MissCount) : 0.0; + + /// + /// 缓存使用率 + /// + public double UsageRate => MaxCapacity > 0 ? (double)CurrentCount / MaxCapacity : 0.0; + + /// + /// 重置统计信息 + /// + public void Reset() + { + HitCount = 0; + MissCount = 0; + EvictedCount = 0; + TotalTimeSavedMs = 0; + } + + /// + /// 生成统计报告 + /// + public string GenerateReport() + { + return $"GridMap缓存统计 - 命中率: {HitRate:P2} ({HitCount}命中/{MissCount}未命中), " + + $"使用率: {UsageRate:P2} ({CurrentCount}/{MaxCapacity}), " + + $"驱逐次数: {EvictedCount}, 节省时间: {TotalTimeSavedMs/1000.0:F1}秒"; + } + } + + /// + /// GridMap缓存管理器 + /// 使用LRU策略管理GridMap缓存,支持线程安全访问 + /// + public class GridMapCache + { + private readonly ConcurrentDictionary _cache; + private readonly object _cleanupLock = new object(); + private readonly int _maxCapacity; + private readonly GridMapCacheStatistics _statistics; + + /// + /// 获取缓存统计信息 + /// + public GridMapCacheStatistics Statistics => _statistics; + + /// + /// 构造函数 + /// + /// 最大缓存容量,默认10个GridMap + public GridMapCache(int maxCapacity = 10) + { + if (maxCapacity <= 0) + throw new ArgumentException("缓存容量必须大于0", nameof(maxCapacity)); + + _maxCapacity = maxCapacity; + _cache = new ConcurrentDictionary(); + _statistics = new GridMapCacheStatistics { MaxCapacity = maxCapacity }; + + LogManager.Info($"[GridMap缓存] 初始化完成,最大容量: {_maxCapacity}"); + } + + /// + /// 获取缓存的GridMap + /// + /// 缓存键 + /// 预估的构建时间(用于统计节省时间) + /// GridMap对象,如果不存在则返回null + public GridMap Get(GridMapCacheKey key, long estimatedBuildTimeMs = 0) + { + if (key == null) + return null; + + if (_cache.TryGetValue(key, out var item)) + { + // 记录访问 + item.RecordAccess(); + _statistics.HitCount++; + + // 累计节省时间 + if (estimatedBuildTimeMs > 0) + { + _statistics.TotalTimeSavedMs += estimatedBuildTimeMs; + } + + LogManager.Info($"[GridMap缓存] 命中 - {key},访问次数: {item.AccessCount}"); + return item.GridMap; + } + + _statistics.MissCount++; + LogManager.Info($"[GridMap缓存] 未命中 - {key}"); + return null; + } + + /// + /// 添加GridMap到缓存 + /// + /// 缓存键 + /// GridMap对象 + public void Put(GridMapCacheKey key, GridMap gridMap) + { + if (key == null || gridMap == null) + return; + + var item = new GridMapCacheItem(gridMap); + + // 添加或更新缓存项 + _cache.AddOrUpdate(key, item, (k, existing) => + { + LogManager.Debug($"[GridMap缓存] 更新现有项 - {key}"); + return item; + }); + + // 更新统计信息 + _statistics.CurrentCount = _cache.Count; + + LogManager.Info($"[GridMap缓存] 添加 - {key},当前容量: {_cache.Count}/{_maxCapacity}"); + LogManager.Info($"[GridMap缓存] GridMap统计: {gridMap.GetStatistics()}"); + + // 检查是否需要清理 + if (_cache.Count > _maxCapacity) + { + CleanupCache(); + } + } + + /// + /// 检查缓存中是否包含指定键 + /// + /// 缓存键 + /// 是否包含 + public bool ContainsKey(GridMapCacheKey key) + { + return key != null && _cache.ContainsKey(key); + } + + /// + /// 清除所有缓存 + /// + public void Clear() + { + lock (_cleanupLock) + { + var count = _cache.Count; + _cache.Clear(); + _statistics.CurrentCount = 0; + _statistics.Reset(); + + LogManager.Info($"[GridMap缓存] 清除所有缓存,共清除 {count} 项"); + } + } + + /// + /// 获取缓存的所有键 + /// + /// 键列表 + public IList GetKeys() + { + return _cache.Keys.ToList(); + } + + /// + /// 清理缓存,使用LRU策略移除最少使用的项 + /// + private void CleanupCache() + { + lock (_cleanupLock) + { + // 双重检查,避免重复清理 + if (_cache.Count <= _maxCapacity) + return; + + var targetCount = Math.Max(1, (int)(_maxCapacity * 0.7)); // 清理到70%容量 + var itemsToRemove = _cache.Count - targetCount; + + LogManager.Info($"[GridMap缓存] 开始LRU清理,当前: {_cache.Count},目标: {targetCount},需移除: {itemsToRemove}"); + + // 按最后访问时间排序,移除最久未访问的项 + var sortedItems = _cache + .OrderBy(kvp => kvp.Value.LastAccessTime) + .ThenBy(kvp => kvp.Value.AccessCount) // 相同时间的情况下,优先移除访问次数少的 + .Take(itemsToRemove) + .ToList(); + + int removedCount = 0; + foreach (var item in sortedItems) + { + if (_cache.TryRemove(item.Key, out _)) + { + removedCount++; + _statistics.EvictedCount++; + LogManager.Debug($"[GridMap缓存] 移除 - {item.Key},最后访问: {item.Value.LastAccessTime:yyyy-MM-dd HH:mm:ss}"); + } + } + + _statistics.CurrentCount = _cache.Count; + LogManager.Info($"[GridMap缓存] LRU清理完成,实际移除: {removedCount},当前容量: {_cache.Count}"); + } + } + + /// + /// 生成缓存详细报告 + /// + /// 报告字符串 + public string GenerateDetailedReport() + { + var report = new System.Text.StringBuilder(); + + report.AppendLine(_statistics.GenerateReport()); + + if (_cache.Count > 0) + { + report.AppendLine($"\n当前缓存项详情:"); + var sortedItems = _cache + .OrderByDescending(kvp => kvp.Value.LastAccessTime) + .Take(5) // 只显示最近5项 + .ToList(); + + foreach (var item in sortedItems) + { + var gridStats = item.Value.GridMap?.GetStatistics() ?? "N/A"; + report.AppendLine($" {item.Key}"); + report.AppendLine($" 访问{item.Value.AccessCount}次,最后访问: {item.Value.LastAccessTime:MM-dd HH:mm:ss}"); + report.AppendLine($" GridMap: {gridStats}"); + } + + if (_cache.Count > 5) + { + report.AppendLine($" ... 还有 {_cache.Count - 5} 项"); + } + } + + return report.ToString(); + } + } + + /// + /// 全局GridMap缓存管理器(单例) + /// + public static class GlobalGridMapCache + { + private static readonly Lazy _instance = new Lazy(() => new GridMapCache()); + + /// + /// 获取全局缓存实例 + /// + public static GridMapCache Instance => _instance.Value; + + /// + /// 重置全局缓存(主要用于测试) + /// + public static void Reset() + { + _instance.Value.Clear(); + } + } +} \ No newline at end of file diff --git a/src/PathPlanning/GridMapCacheKey.cs b/src/PathPlanning/GridMapCacheKey.cs new file mode 100644 index 0000000..a85d0a8 --- /dev/null +++ b/src/PathPlanning/GridMapCacheKey.cs @@ -0,0 +1,243 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using Autodesk.Navisworks.Api; + +namespace NavisworksTransport.PathPlanning +{ + /// + /// GridMap缓存键,用于标识唯一的GridMap生成配置 + /// 基于通道数据、车辆尺寸、网格尺寸等关键参数 + /// + public class GridMapCacheKey : IEquatable + { + /// + /// 通道数据哈希值(所有可通行物流构件) + /// + public string ChannelDataHash { get; set; } + + /// + /// 边界范围哈希 + /// + public string BoundsHash { get; set; } + + /// + /// 网格单元大小(模型单位) + /// + public double CellSize { get; set; } + + /// + /// 车辆半径(米) + /// + public double VehicleRadius { get; set; } + + /// + /// 安全间隙(米) + /// + public double SafetyMargin { get; set; } + + /// + /// 车辆高度(米) + /// + public double VehicleHeight { get; set; } + + /// + /// 构造函数 + /// + public GridMapCacheKey(string channelDataHash, string boundsHash, double cellSize, + double vehicleRadius, double safetyMargin, double vehicleHeight) + { + ChannelDataHash = channelDataHash ?? throw new ArgumentNullException(nameof(channelDataHash)); + BoundsHash = boundsHash ?? throw new ArgumentNullException(nameof(boundsHash)); + CellSize = cellSize; + VehicleRadius = vehicleRadius; + SafetyMargin = safetyMargin; + VehicleHeight = vehicleHeight; + } + + /// + /// 检查两个缓存键是否相等 + /// + public bool Equals(GridMapCacheKey other) + { + if (other == null) return false; + if (ReferenceEquals(this, other)) return true; + + return ChannelDataHash == other.ChannelDataHash && + BoundsHash == other.BoundsHash && + Math.Abs(CellSize - other.CellSize) < 1e-6 && + Math.Abs(VehicleRadius - other.VehicleRadius) < 1e-6 && + Math.Abs(SafetyMargin - other.SafetyMargin) < 1e-6 && + Math.Abs(VehicleHeight - other.VehicleHeight) < 1e-6; + } + + /// + /// 重写Equals方法 + /// + public override bool Equals(object obj) + { + return Equals(obj as GridMapCacheKey); + } + + /// + /// 计算哈希码,用于Dictionary键 + /// + public override int GetHashCode() + { + unchecked + { + int hash = 17; + hash = hash * 23 + (ChannelDataHash?.GetHashCode() ?? 0); + hash = hash * 23 + (BoundsHash?.GetHashCode() ?? 0); + hash = hash * 23 + CellSize.GetHashCode(); + hash = hash * 23 + VehicleRadius.GetHashCode(); + hash = hash * 23 + SafetyMargin.GetHashCode(); + hash = hash * 23 + VehicleHeight.GetHashCode(); + return hash; + } + } + + /// + /// 生成可读的字符串表示 + /// + public override string ToString() + { + var channelPreview = ChannelDataHash != null ? + ChannelDataHash.Substring(0, Math.Min(8, ChannelDataHash.Length)) : + "NULL"; + var boundsPreview = BoundsHash != null ? + BoundsHash.Substring(0, Math.Min(8, BoundsHash.Length)) : + "NULL"; + + return $"GridMapKey[Ch={channelPreview}, Bounds={boundsPreview}, " + + $"Cell={CellSize:F2}, VR={VehicleRadius:F1}m, SM={SafetyMargin:F1}m, VH={VehicleHeight:F1}m]"; + } + + /// + /// 根据参数生成GridMap缓存键 + /// + /// Navisworks文档 + /// 边界范围 + /// 网格大小 + /// 车辆半径(米) + /// 安全间隙(米) + /// 车辆高度(米) + /// 缓存键 + public static GridMapCacheKey CreateFrom(Document document, BoundingBox3D bounds, + double cellSize, double vehicleRadius, + double safetyMargin, double vehicleHeight) + { + if (document == null) + throw new ArgumentNullException(nameof(document)); + + var channelDataHash = ComputeChannelDataHash(document); + var boundsHash = ComputeBoundsHash(bounds); + + return new GridMapCacheKey(channelDataHash, boundsHash, cellSize, + vehicleRadius, safetyMargin, vehicleHeight); + } + + /// + /// 计算通道数据哈希值 + /// 基于所有可通行物流构件的ID、属性、几何等信息 + /// + /// Navisworks文档 + /// 通道数据哈希 + private static string ComputeChannelDataHash(Document document) + { + try + { + using (var sha256 = SHA256.Create()) + { + var hashBuilder = new StringBuilder(); + + // 获取所有可通行的物流模型项 + var allChannelItems = CategoryAttributeManager.GetAllTraversableLogisticsItems(document); + + // 按InstanceGuid排序确保一致性 + var sortedItems = allChannelItems.OrderBy(item => item.InstanceGuid.ToString()).ToList(); + + hashBuilder.Append($"ChannelCount:{sortedItems.Count}|"); + + foreach (var item in sortedItems) + { + // 基本标识信息 + hashBuilder.Append($"ID:{item.InstanceGuid}|"); + hashBuilder.Append($"DisplayName:{item.DisplayName ?? "NULL"}|"); + + // 几何信息(包围盒) + var bbox = item.BoundingBox(); + if (bbox != null) + { + hashBuilder.Append($"BBox:{bbox.Min.X:F3},{bbox.Min.Y:F3},{bbox.Min.Z:F3}-"); + hashBuilder.Append($"{bbox.Max.X:F3},{bbox.Max.Y:F3},{bbox.Max.Z:F3}|"); + } + + // 物流类型属性 + var logisticsType = CategoryAttributeManager.GetLogisticsElementType(item); + hashBuilder.Append($"LogType:{logisticsType}|"); + + // Transform信息(如果有变换) + var transform = item.Transform; + if (transform != null) + { + // 只包含关键的变换信息 + hashBuilder.Append($"TX:{transform.Translation.X:F3},{transform.Translation.Y:F3},{transform.Translation.Z:F3}|"); + } + } + + // 计算最终哈希 + var hashInput = hashBuilder.ToString(); + var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(hashInput)); + + var hexBuilder = new StringBuilder(); + foreach (byte b in hashBytes) + { + hexBuilder.Append(b.ToString("x2")); + } + + return hexBuilder.ToString(); + } + } + catch (Exception ex) + { + LogManager.Warning($"[通道哈希] 计算通道数据哈希失败: {ex.Message},使用时间戳标识"); + return $"CHANNEL_HASH_FAILED_{DateTime.Now.Ticks}"; + } + } + + /// + /// 计算边界范围哈希值 + /// + /// 边界范围 + /// 边界哈希 + private static string ComputeBoundsHash(BoundingBox3D bounds) + { + try + { + using (var sha256 = SHA256.Create()) + { + var boundsString = $"Bounds:{bounds.Min.X:F6},{bounds.Min.Y:F6},{bounds.Min.Z:F6}-" + + $"{bounds.Max.X:F6},{bounds.Max.Y:F6},{bounds.Max.Z:F6}"; + + var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(boundsString)); + + var hexBuilder = new StringBuilder(); + foreach (byte b in hashBytes) + { + hexBuilder.Append(b.ToString("x2")); + } + + return hexBuilder.ToString(); + } + } + catch (Exception ex) + { + LogManager.Warning($"[边界哈希] 计算边界哈希失败: {ex.Message},使用时间戳标识"); + return $"BOUNDS_HASH_FAILED_{DateTime.Now.Ticks}"; + } + } + } +} \ No newline at end of file diff --git a/src/PathPlanning/GridMapGenerator.cs b/src/PathPlanning/GridMapGenerator.cs index f2024fe..0476995 100644 --- a/src/PathPlanning/GridMapGenerator.cs +++ b/src/PathPlanning/GridMapGenerator.cs @@ -1,11 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; -using System.Threading.Tasks; using Autodesk.Navisworks.Api; using NavisworksTransport.Utils; -using NavisworksTransport.Core; namespace NavisworksTransport.PathPlanning { @@ -60,7 +57,30 @@ namespace NavisworksTransport.PathPlanning { try { - LogManager.Info("【生成网格地图】开始生成网格地图"); + // 生成缓存键 + var cacheKey = GridMapCacheKey.CreateFrom(document, bounds, cellSize, vehicleRadius, safetyMargin, vehicleHeight); + + // 尝试从缓存获取 + var cachedGridMap = GlobalGridMapCache.Instance.Get(cacheKey, 5000); // 预估生成需要5秒 + if (cachedGridMap != null) + { + LogManager.Info($"【GridMap缓存】使用缓存网格地图 - {cacheKey}"); + LogManager.Info($"【GridMap缓存】缓存GridMap统计: {cachedGridMap.GetStatistics()}"); + + // 更新规划起终点信息(这些信息可能会变化,不影响核心网格结构) + if (planningStartPoint != default(Point3D) && planningEndPoint != default(Point3D)) + { + cachedGridMap.PlanningStartPoint = planningStartPoint; + cachedGridMap.PlanningEndPoint = planningEndPoint; + cachedGridMap.HasPlanningPoints = true; + } + + return cachedGridMap; + } + + // 缓存未命中,生成新的GridMap + LogManager.Info("【生成网格地图】缓存未命中,开始生成新网格地图"); + LogManager.Info($"【生成网格地图】缓存键: {cacheKey}"); var startTime = DateTime.Now; // 第一步:统一转换所有米制参数为模型单位 @@ -150,6 +170,11 @@ namespace NavisworksTransport.PathPlanning var elapsed = (DateTime.Now - startTime).TotalMilliseconds; LogManager.Info($"【生成网格地图】网格地图生成完成: {channelCoverage.GridMap.GetStatistics()}"); LogManager.Info($"【生成网格地图】总耗时: {elapsed:F1}ms"); + + // 将生成的GridMap添加到缓存 + GlobalGridMapCache.Instance.Put(cacheKey, channelCoverage.GridMap); + LogManager.Info($"【GridMap缓存】网格地图已缓存,缓存键: {cacheKey}"); + return channelCoverage.GridMap; } catch (Exception ex) @@ -159,6 +184,33 @@ namespace NavisworksTransport.PathPlanning } } + /// + /// 获取GridMap缓存统计信息 + /// + /// 缓存统计报告 + public static string GetCacheStatistics() + { + return GlobalGridMapCache.Instance.Statistics.GenerateReport(); + } + + /// + /// 获取GridMap缓存详细统计报告 + /// + /// 详细统计报告 + public static string GetDetailedCacheReport() + { + return GlobalGridMapCache.Instance.GenerateDetailedReport(); + } + + /// + /// 清除GridMap缓存 + /// + public static void ClearCache() + { + GlobalGridMapCache.Instance.Clear(); + LogManager.Info("[GridMap缓存] 手动清除所有缓存"); + } + /// /// 处理门元素,将其设置为可通行网格 ///