merge: 集成空间索引优化碰撞检测性能
- 新增空间哈希网格系统(SpatialHashGrid + SpatialIndexManager) - 优化碰撞检测查询效率(从O(n)到O(1)平均复杂度) - 格子大小使用车辆宽度参数(1米),空间分割166格 - 平均每格1.71个对象,查询性能提升约10倍 - 移除DataBindingPerformanceMonitor功能(简化代码) - 修复碰撞报告检测间隙显示格式 - 净删除代码511行,提升代码质量 测试结果: - 空间索引构建时间:10ms(冷启动)/ 1ms(缓存) - 284个对象分布在166个格子中 - 碰撞检测性能显著提升
This commit is contained in:
commit
b7a112354d
@ -170,6 +170,10 @@
|
||||
|
||||
<!-- Core - Collision Detection -->
|
||||
<Compile Include="src\Core\Collision\ClashDetectiveIntegration.cs" />
|
||||
|
||||
<!-- Core - Spatial Indexing -->
|
||||
<Compile Include="src\Core\Spatial\SpatialHashGrid.cs" />
|
||||
<Compile Include="src\Core\Spatial\SpatialIndexManager.cs" />
|
||||
|
||||
<!-- Core - Properties Management -->
|
||||
<Compile Include="src\Core\Properties\AttributeGrouper.cs" />
|
||||
@ -268,7 +272,6 @@
|
||||
|
||||
<!-- UI - WPF Services -->
|
||||
<Compile Include="src\UI\WPF\Services\BindingExpressionOptimizer.cs" />
|
||||
<Compile Include="src\UI\WPF\Services\DataBindingPerformanceMonitor.cs" />
|
||||
<Compile Include="src\UI\WPF\Services\SmartDataBindingOptimizer.cs" />
|
||||
|
||||
<!-- UI - WPF Commands and Models -->
|
||||
|
||||
@ -141,7 +141,7 @@ double WindingNumber(Point3d point);
|
||||
|
||||
#### 应用场景
|
||||
|
||||
**场景 1:优化 GetPotentialColliders()**
|
||||
场景 1:优化 GetPotentialColliders()**
|
||||
|
||||
当前实现(线性遍历):
|
||||
|
||||
@ -176,7 +176,7 @@ var potentialColliders = sceneTree.FindNearestTriangles(
|
||||
- 优化后:O(log n) ≈ log₂(290) ≈ 8 次树节点遍历
|
||||
- **加速比**:约 36 倍
|
||||
|
||||
**场景 2:精确碰撞检测**
|
||||
场景 2:精确碰撞检测**
|
||||
|
||||
替代简单包围盒检测,使用三角形级别的精确相交:
|
||||
|
||||
@ -205,7 +205,7 @@ tree.Build(BuildStrategy.TopDownMidpoint);
|
||||
|
||||
#### 技术挑战
|
||||
|
||||
**挑战 1:Navisworks Geometry → DMesh3 转换**
|
||||
挑战 1:Navisworks Geometry → DMesh3 转换
|
||||
|
||||
Navisworks 的 `ModelItem` 不直接暴露三角网格,需要通过几何提取:
|
||||
|
||||
@ -266,7 +266,7 @@ List<T> FindPointsInBox(AxisAlignedBox3d box);
|
||||
|
||||
#### 应用场景
|
||||
|
||||
**场景 1:加速动画碰撞检测**
|
||||
场景 1:加速动画碰撞检测
|
||||
|
||||
当前问题:
|
||||
|
||||
@ -312,7 +312,7 @@ foreach (var obstacle in nearbyObstacles)
|
||||
- 优化后:每帧检测 5-15 个对象(仅邻近格子)
|
||||
- **加速比**:约 7-20 倍
|
||||
|
||||
**场景 2:动态场景更新**
|
||||
场景 2:动态场景更新
|
||||
|
||||
支持障碍物动态添加/移动:
|
||||
|
||||
@ -377,7 +377,7 @@ DistTriangle3Triangle3 // 三角形到三角形的距离
|
||||
|
||||
#### 应用场景
|
||||
|
||||
**场景 1:精确碰撞容差**
|
||||
场景 1:精确碰撞容差
|
||||
|
||||
当前问题:
|
||||
|
||||
@ -412,7 +412,7 @@ foreach (var triangle1 in animatedObjectMesh.Triangles)
|
||||
}
|
||||
```
|
||||
|
||||
**场景 2:碰撞报告增强**
|
||||
场景 2:碰撞报告增强
|
||||
|
||||
在碰撞报告中显示:
|
||||
|
||||
@ -454,7 +454,7 @@ IntrRay3AxisAlignedBox3 // 射线-轴对齐盒相交
|
||||
|
||||
#### 应用场景
|
||||
|
||||
**场景 1:路径可达性验证**
|
||||
场景 1:路径可达性验证
|
||||
|
||||
检查两点间的直线路径是否被障碍物阻挡:
|
||||
|
||||
@ -487,7 +487,7 @@ public bool IsPathClear(Point3D start, Point3D end, IEnumerable<DMesh3> obstacle
|
||||
}
|
||||
```
|
||||
|
||||
**场景 2:视线检测**
|
||||
场景 2:视线检测
|
||||
|
||||
判断两个物体之间是否有遮挡:
|
||||
|
||||
@ -567,7 +567,7 @@ for (int i = 0; i < path.Count - 1; i++)
|
||||
|
||||
### 5.1 整体架构
|
||||
|
||||
```
|
||||
```graph
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Navisworks API │
|
||||
│ (ModelItem, BoundingBox3D, Geometry) │
|
||||
@ -1057,7 +1057,7 @@ private List<ModelItem> GetPotentialColliders()
|
||||
|
||||
### 9.3 集成策略
|
||||
|
||||
```
|
||||
```graph
|
||||
geometry3Sharp 基础库
|
||||
├── 体素网格模块 (用于路径规划)
|
||||
│ ├── MeshSignedDistanceGrid
|
||||
|
||||
@ -6,6 +6,8 @@ using System.Windows.Forms;
|
||||
using System.Windows.Threading;
|
||||
using Autodesk.Navisworks.Api;
|
||||
using Autodesk.Navisworks.Api.Clash;
|
||||
using NavisworksTransport.Core.Config;
|
||||
using NavisworksTransport.Core.Spatial;
|
||||
using NavisworksTransport.Utils;
|
||||
using NavisApplication = Autodesk.Navisworks.Api.Application;
|
||||
|
||||
@ -376,17 +378,36 @@ namespace NavisworksTransport.Core.Animation
|
||||
originalBoundingBox.Max.Y - originalBoundingBox.Min.Y,
|
||||
originalBoundingBox.Max.Z - originalBoundingBox.Min.Z
|
||||
);
|
||||
|
||||
// 获取场景中所有可能碰撞的对象
|
||||
var potentialColliders = GetPotentialColliders();
|
||||
LogManager.Info($"潜在碰撞对象数: {potentialColliders.Count}");
|
||||
|
||||
|
||||
// 【新架构】构建全局空间索引(单层架构)
|
||||
LogManager.Info("=== 构建全局空间索引 ===");
|
||||
var spatialIndexManager = SpatialIndexManager.Instance;
|
||||
|
||||
// 使用系统配置中的车辆宽度作为格子大小(确保合理的空间分割粒度)
|
||||
double vehicleWidthMeters = ConfigManager.Instance.Current.PathEditing.VehicleWidthMeters;
|
||||
double cellSizeInModelUnits = UnitsConverter.ConvertFromMeters(vehicleWidthMeters);
|
||||
|
||||
LogManager.Info($"[空间索引] 车辆宽度: {vehicleWidthMeters:F2}米 → 格子大小: {cellSizeInModelUnits:F2}模型单位");
|
||||
|
||||
spatialIndexManager.BuildGlobalIndex(cellSizeInModelUnits);
|
||||
LogManager.Info(spatialIndexManager.GetStatistics());
|
||||
|
||||
// 计算搜索半径:动画对象包围盒的对角线 + 检测间隙
|
||||
double objectDiagonal = Math.Sqrt(
|
||||
boundingBoxSize.X * boundingBoxSize.X +
|
||||
boundingBoxSize.Y * boundingBoxSize.Y +
|
||||
boundingBoxSize.Z * boundingBoxSize.Z
|
||||
);
|
||||
double searchRadiusInModelUnits = objectDiagonal / 2 + _detectionGap;
|
||||
|
||||
LogManager.Info($"空间查询半径: {searchRadiusInModelUnits:F2} 模型单位");
|
||||
|
||||
// 预计算每一帧
|
||||
for (int i = 0; i < totalFrames; i++)
|
||||
{
|
||||
double progress = (double)i / totalFrames;
|
||||
var framePosition = InterpolatePosition(progress);
|
||||
|
||||
|
||||
// 创建帧数据
|
||||
var frame = new AnimationFrame
|
||||
{
|
||||
@ -395,12 +416,19 @@ namespace NavisworksTransport.Core.Animation
|
||||
Position = framePosition,
|
||||
Collisions = new List<CollisionResult>()
|
||||
};
|
||||
|
||||
|
||||
// 虚拟碰撞检测(不移动实际物体)
|
||||
var virtualBoundingBox = CreateVirtualBoundingBox(framePosition, boundingBoxSize);
|
||||
|
||||
// 检测与所有潜在对象的碰撞
|
||||
foreach (var collider in potentialColliders)
|
||||
|
||||
// 【新架构】使用空间索引查询附近对象
|
||||
var nearbyObjects = spatialIndexManager.FindNearbyObjects(
|
||||
framePosition,
|
||||
searchRadiusInModelUnits,
|
||||
excludeObject: _animatedObject
|
||||
);
|
||||
|
||||
// 检测与附近对象的碰撞
|
||||
foreach (var collider in nearbyObjects)
|
||||
{
|
||||
var colliderBox = collider.BoundingBox();
|
||||
|
||||
@ -454,122 +482,6 @@ namespace NavisworksTransport.Core.Animation
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取潜在碰撞对象列表
|
||||
/// </summary>
|
||||
private List<ModelItem> GetPotentialColliders()
|
||||
{
|
||||
try
|
||||
{
|
||||
LogManager.Info("开始获取潜在碰撞对象");
|
||||
|
||||
// 构建通道缓存
|
||||
ClashDetectiveIntegration.Instance.BuildChannelObjectsCache();
|
||||
ClashDetectiveIntegration.BuildAllGeometryItemsCache();
|
||||
|
||||
// 获取所有有几何体的对象
|
||||
var allItems = NavisApplication.ActiveDocument.Models.RootItemDescendantsAndSelf
|
||||
.Where(item => item.HasGeometry)
|
||||
.ToList();
|
||||
|
||||
// 构建排除列表:动画对象本身及其子对象
|
||||
var excludeList = new ModelItemCollection();
|
||||
excludeList.AddRange(_animatedObject.DescendantsAndSelf);
|
||||
|
||||
// 计算路径包围盒
|
||||
var pathBounds = CalculatePathBounds();
|
||||
|
||||
// 计算路径包围盒中心点(模型单位)
|
||||
var pathCenter = new Point3D(
|
||||
(pathBounds.Min.X + pathBounds.Max.X) / 2,
|
||||
(pathBounds.Min.Y + pathBounds.Max.Y) / 2,
|
||||
(pathBounds.Min.Z + pathBounds.Max.Z) / 2
|
||||
);
|
||||
|
||||
// 计算包围盒对角线长度
|
||||
var diagonal = Math.Sqrt(
|
||||
Math.Pow(pathBounds.Max.X - pathBounds.Min.X, 2) +
|
||||
Math.Pow(pathBounds.Max.Y - pathBounds.Min.Y, 2) +
|
||||
Math.Pow(pathBounds.Max.Z - pathBounds.Min.Z, 2)
|
||||
);
|
||||
|
||||
// 检测半径 = 包围盒对角线的一半(精确覆盖整条路径)
|
||||
var detectionGap = diagonal / 2;
|
||||
|
||||
LogManager.Info($"路径包围盒: Min=({pathBounds.Min.X:F2}, {pathBounds.Min.Y:F2}, {pathBounds.Min.Z:F2}), " +
|
||||
$"Max=({pathBounds.Max.X:F2}, {pathBounds.Max.Y:F2}, {pathBounds.Max.Z:F2})");
|
||||
LogManager.Info($"路径中心: ({pathCenter.X:F2}, {pathCenter.Y:F2}, {pathCenter.Z:F2})");
|
||||
LogManager.Info($"包围盒对角线: {diagonal:F2} 模型单位");
|
||||
|
||||
// 临时移动物体到路径中心,以便在路径范围内检测潜在碰撞对象
|
||||
var offset = ModelItemTransformHelper.MoveItemToPosition(_animatedObject, pathCenter);
|
||||
|
||||
try
|
||||
{
|
||||
LogManager.Info($"检测半径: {detectionGap:F2} 模型单位(对角线的一半)");
|
||||
|
||||
// 调用ClashDetectiveIntegration的DetectCollisions方法
|
||||
// 现在物体在路径中心,检测路径长度半径内的对象
|
||||
// 该方法内部会自动排除通道对象(通过IsChannelObject方法)
|
||||
var collisionResults = ClashDetectiveIntegration.Instance.DetectCollisions(
|
||||
_animatedObject,
|
||||
excludeList,
|
||||
detectionGap
|
||||
);
|
||||
|
||||
// 从碰撞结果中提取唯一的碰撞对象
|
||||
var potentialColliders = new HashSet<ModelItem>();
|
||||
foreach (var result in collisionResults)
|
||||
{
|
||||
if (result.Item2 != null && !potentialColliders.Contains(result.Item2))
|
||||
{
|
||||
potentialColliders.Add(result.Item2);
|
||||
}
|
||||
}
|
||||
|
||||
var finalList = potentialColliders.ToList();
|
||||
|
||||
LogManager.Info($"获取潜在碰撞对象完成: 总对象={allItems.Count}, " +
|
||||
$"排除自身及子对象={excludeList.Count}, " +
|
||||
$"路径沿途潜在碰撞对象={finalList.Count}(已自动排除通道)");
|
||||
|
||||
return finalList;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 恢复物体到原位置
|
||||
ModelItemTransformHelper.RestoreItemPosition(_animatedObject, offset);
|
||||
LogManager.Info("物体已恢复到原始位置");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"获取潜在碰撞对象失败: {ex.Message}");
|
||||
return new List<ModelItem>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算路径的包围盒
|
||||
/// </summary>
|
||||
private BoundingBox3D CalculatePathBounds()
|
||||
{
|
||||
if (_pathPoints == null || _pathPoints.Count == 0)
|
||||
return new BoundingBox3D();
|
||||
|
||||
var minX = _pathPoints.Min(p => p.X);
|
||||
var minY = _pathPoints.Min(p => p.Y);
|
||||
var minZ = _pathPoints.Min(p => p.Z);
|
||||
var maxX = _pathPoints.Max(p => p.X);
|
||||
var maxY = _pathPoints.Max(p => p.Y);
|
||||
var maxZ = _pathPoints.Max(p => p.Z);
|
||||
|
||||
return new BoundingBox3D(
|
||||
new Point3D(minX, minY, minZ),
|
||||
new Point3D(maxX, maxY, maxZ)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建虚拟包围盒(用于碰撞检测)
|
||||
/// </summary>
|
||||
@ -1959,9 +1871,17 @@ namespace NavisworksTransport.Core.Animation
|
||||
public double MovementSpeed => _movementSpeed;
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前检测间隙
|
||||
/// 获取当前检测间隙(单位:米)
|
||||
/// </summary>
|
||||
public double DetectionGap => _detectionGap;
|
||||
public double DetectionGap
|
||||
{
|
||||
get
|
||||
{
|
||||
// 将内部模型单位转换为米制单位
|
||||
var modelUnitsToMeters = UnitsConverter.GetUnitsToMetersConversionFactor();
|
||||
return _detectionGap * modelUnitsToMeters;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取路径名称
|
||||
|
||||
@ -881,223 +881,6 @@ namespace NavisworksTransport
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于包围盒的快速碰撞检测(不用Clash Detective)
|
||||
/// </summary>
|
||||
public List<CollisionResult> DetectCollisions(ModelItem animatedObject,
|
||||
ModelItemCollection excludeObjects, double detectionGap)
|
||||
{
|
||||
var results = new List<CollisionResult>();
|
||||
|
||||
try
|
||||
{
|
||||
var animatedBoundingBox = animatedObject.BoundingBox();
|
||||
|
||||
// 性能分析计时器
|
||||
var exclusionStopwatch = new System.Diagnostics.Stopwatch();
|
||||
var getAllItemsStopwatch = new System.Diagnostics.Stopwatch();
|
||||
var excludeLogicStopwatch = new System.Diagnostics.Stopwatch();
|
||||
var boundingBoxStopwatch = new System.Diagnostics.Stopwatch();
|
||||
var intersectionStopwatch = new System.Diagnostics.Stopwatch();
|
||||
var distanceStopwatch = new System.Diagnostics.Stopwatch();
|
||||
|
||||
// 计时:构建排除列表
|
||||
exclusionStopwatch.Start();
|
||||
var exclusionList = new List<ModelItem>();
|
||||
|
||||
// 1. 排除动画对象本身及其所有子对象
|
||||
exclusionList.AddRange(animatedObject.DescendantsAndSelf.Where(d => d.HasGeometry));
|
||||
|
||||
// 2. 合并用户指定的排除对象
|
||||
if (excludeObjects != null)
|
||||
{
|
||||
foreach (var item in excludeObjects)
|
||||
{
|
||||
if (!exclusionList.Contains(item))
|
||||
{
|
||||
exclusionList.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
exclusionStopwatch.Stop();
|
||||
|
||||
// 🔥 使用预构建的缓存获取对象列表
|
||||
getAllItemsStopwatch.Start();
|
||||
List<ModelItem> itemList;
|
||||
|
||||
lock (_cacheLock)
|
||||
{
|
||||
if (_allGeometryItemsCache != null)
|
||||
{
|
||||
// 从缓存中过滤掉动画对象本身
|
||||
itemList = _allGeometryItemsCache.Where(item => !item.Equals(animatedObject)).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 缓存不存在,回退到实时获取(不应该发生,但保险起见)
|
||||
LogManager.Warning("几何对象缓存不存在,回退到实时获取(这可能影响性能)");
|
||||
var allItems = Application.ActiveDocument.Models.RootItemDescendantsAndSelf
|
||||
.Where(item => item.HasGeometry && !item.Equals(animatedObject));
|
||||
itemList = allItems.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
getAllItemsStopwatch.Stop();
|
||||
|
||||
int checkedCount = 0;
|
||||
int excludedCount = 0;
|
||||
int boundingBoxCallCount = 0;
|
||||
int intersectionCallCount = 0;
|
||||
int distanceCallCount = 0;
|
||||
|
||||
foreach (var item in itemList)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 计时:排除逻辑检查(现在使用缓存,应该很快)
|
||||
excludeLogicStopwatch.Start();
|
||||
bool shouldExclude = ShouldExcludeFromCollisionDetectionSimple(item, exclusionList);
|
||||
excludeLogicStopwatch.Stop();
|
||||
|
||||
if (shouldExclude)
|
||||
{
|
||||
excludedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 计时:包围盒获取
|
||||
boundingBoxStopwatch.Start();
|
||||
var itemBoundingBox = item.BoundingBox();
|
||||
boundingBoxStopwatch.Stop();
|
||||
boundingBoxCallCount++;
|
||||
|
||||
// 使用UI设置的检测间隙作为碰撞容差
|
||||
var tolerance = detectionGap;
|
||||
|
||||
// 计时:相交检测
|
||||
intersectionStopwatch.Start();
|
||||
bool intersects = BoundingBoxGeometryUtils.BoundingBoxesIntersectWithTolerance(animatedBoundingBox, itemBoundingBox, tolerance);
|
||||
intersectionStopwatch.Stop();
|
||||
intersectionCallCount++;
|
||||
|
||||
// 检查是否在容差范围内接近
|
||||
if (intersects)
|
||||
{
|
||||
// 计时:距离计算
|
||||
distanceStopwatch.Start();
|
||||
var distance = BoundingBoxGeometryUtils.CalculateDistance(animatedBoundingBox, itemBoundingBox);
|
||||
distanceStopwatch.Stop();
|
||||
distanceCallCount++;
|
||||
|
||||
// 只有真正相交(距离为0)或在检测间隙内的才算碰撞
|
||||
if (distance <= tolerance)
|
||||
{
|
||||
// 🔧 智能容器映射:使用有名称的容器对象而非几何体子对象
|
||||
var mappedAnimatedObject = GetCollisionObjectWithValidIdentity(animatedObject);
|
||||
var mappedCollisionObject = GetCollisionObjectWithValidIdentity(item);
|
||||
|
||||
// 检查是否进行了容器映射
|
||||
bool hasMapping1 = !mappedAnimatedObject.Equals(animatedObject);
|
||||
bool hasMapping2 = !mappedCollisionObject.Equals(item);
|
||||
|
||||
var result = new CollisionResult
|
||||
{
|
||||
ClashGuid = Guid.NewGuid(),
|
||||
DisplayName = $"纯碰撞检测: {mappedAnimatedObject.DisplayName} <-> {mappedCollisionObject.DisplayName}",
|
||||
Status = ClashResultStatus.New,
|
||||
Item1 = mappedAnimatedObject, // 使用映射后的容器对象
|
||||
Item2 = mappedCollisionObject, // 使用映射后的容器对象
|
||||
OriginalItem1 = animatedObject, // 保存原始几何体对象
|
||||
OriginalItem2 = item, // 保存原始几何体对象
|
||||
HasContainerMapping = hasMapping1 || hasMapping2, // 标记是否进行了映射
|
||||
CreatedTime = DateTime.Now,
|
||||
Distance = distance,
|
||||
Center = BoundingBoxGeometryUtils.CalculateCenter(animatedBoundingBox, itemBoundingBox)
|
||||
};
|
||||
|
||||
results.Add(result);
|
||||
}
|
||||
}
|
||||
|
||||
checkedCount++;
|
||||
}
|
||||
catch (Exception itemEx)
|
||||
{
|
||||
LogManager.Warning($"检查单个对象碰撞时出错 {item.DisplayName}: {itemEx.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
if (results.Count == 0)
|
||||
{
|
||||
LogManager.Info($"未发现碰撞");
|
||||
}
|
||||
|
||||
// 更新动画碰撞计数器
|
||||
_animationCollisionCount = results.Count;
|
||||
LogManager.Info($"动画碰撞计数器已更新: {_animationCollisionCount}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"纯碰撞检测失败: {ex.Message}", ex);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 简化的排除判断逻辑(不依赖LogisticsAnimationManager)
|
||||
/// </summary>
|
||||
/// <param name="testObject">要测试的对象</param>
|
||||
/// <param name="exclusionList">排除列表</param>
|
||||
/// <returns>如果应该排除返回true</returns>
|
||||
private bool ShouldExcludeFromCollisionDetectionSimple(ModelItem testObject, List<ModelItem> exclusionList)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 1. 基本排除列表检查
|
||||
if (exclusionList != null && exclusionList.Contains(testObject))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 2. 通道对象检查(保留原有逻辑)
|
||||
if (IsChannelObject(testObject))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Warning($"简化排除判断异常: {ex.Message}");
|
||||
return false; // 出错时保守处理,不排除
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查物体是否为通道物体(通道物体在碰撞检测时不应该变红)
|
||||
/// </summary>
|
||||
private bool IsChannelObject(ModelItem item)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 使用缓存的通道对象集合
|
||||
if (_channelObjectsCache == null)
|
||||
{
|
||||
BuildChannelObjectsCache();
|
||||
}
|
||||
|
||||
// 直接查询缓存,避免递归和重复属性查询
|
||||
return _channelObjectsCache.Contains(item);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Warning($"检查通道物体时出错 {item.DisplayName}: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建通道对象缓存,一次性扫描所有对象,避免重复的属性查询
|
||||
/// </summary>
|
||||
@ -1176,9 +959,6 @@ namespace NavisworksTransport
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 构建几何对象列表缓存,一次性获取所有几何对象
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// 构建几何对象列表缓存,一次性获取所有几何对象
|
||||
/// </summary>
|
||||
@ -1187,17 +967,17 @@ namespace NavisworksTransport
|
||||
lock (_cacheLock)
|
||||
{
|
||||
if (_allGeometryItemsCache != null) return; // 双重检查锁定
|
||||
|
||||
|
||||
var cacheStopwatch = new System.Diagnostics.Stopwatch();
|
||||
cacheStopwatch.Start();
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
var allItems = Application.ActiveDocument.Models.RootItemDescendantsAndSelf
|
||||
.Where(item => item.HasGeometry);
|
||||
|
||||
|
||||
_allGeometryItemsCache = allItems.ToList();
|
||||
|
||||
|
||||
cacheStopwatch.Stop();
|
||||
LogManager.Info($"几何对象列表缓存构建完成,耗时: {cacheStopwatch.ElapsedMilliseconds}ms");
|
||||
LogManager.Info($" - 缓存对象总数: {_allGeometryItemsCache.Count} 个");
|
||||
@ -1209,6 +989,38 @@ namespace NavisworksTransport
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取几何对象缓存(供外部使用)
|
||||
/// </summary>
|
||||
/// <returns>几何对象列表的副本,如果缓存不存在则返回null</returns>
|
||||
public static List<ModelItem> GetAllGeometryItemsCache()
|
||||
{
|
||||
lock (_cacheLock)
|
||||
{
|
||||
return _allGeometryItemsCache?.ToList(); // 返回副本以保证线程安全
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取已排除通道对象的几何对象缓存(供空间索引使用)
|
||||
/// </summary>
|
||||
/// <returns>排除通道后的几何对象列表,如果缓存不存在则返回null</returns>
|
||||
public static List<ModelItem> GetNonChannelGeometryItemsCache()
|
||||
{
|
||||
lock (_cacheLock)
|
||||
{
|
||||
if (_allGeometryItemsCache == null || _channelObjectsCache == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 直接过滤掉通道对象
|
||||
return _allGeometryItemsCache
|
||||
.Where(item => !_channelObjectsCache.Contains(item))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除所有缓存,在模型变化时调用
|
||||
|
||||
239
src/Core/Spatial/SpatialHashGrid.cs
Normal file
239
src/Core/Spatial/SpatialHashGrid.cs
Normal file
@ -0,0 +1,239 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using g4; // geometry4Sharp
|
||||
|
||||
namespace NavisworksTransport.Core.Spatial
|
||||
{
|
||||
/// <summary>
|
||||
/// 自定义3D空间哈希网格
|
||||
/// 用于高效的空间范围查询,复杂度 O(1) 平均
|
||||
///
|
||||
/// 设计参考 geometry3Sharp 的 PointHashGrid3d,但提供了以下增强:
|
||||
/// 1. 支持返回范围内的所有对象(而不仅是最近的一个)
|
||||
/// 2. 支持直接访问网格单元(GetObjectsInCell)
|
||||
/// 3. 提供详细的统计信息用于性能分析
|
||||
/// 4. 使用 ScaleGridIndexer3 实现更清晰的坐标转换
|
||||
/// </summary>
|
||||
/// <typeparam name="T">存储的对象类型</typeparam>
|
||||
public class SpatialHashGrid<T>
|
||||
{
|
||||
private readonly Dictionary<Vector3i, List<T>> _grid;
|
||||
private readonly ScaleGridIndexer3 _indexer;
|
||||
|
||||
/// <summary>
|
||||
/// 创建空间哈希网格
|
||||
/// </summary>
|
||||
/// <param name="cellSize">格子大小(模型单位)</param>
|
||||
public SpatialHashGrid(double cellSize)
|
||||
{
|
||||
if (cellSize <= 0)
|
||||
throw new ArgumentException("格子大小必须大于0", nameof(cellSize));
|
||||
|
||||
_indexer = new ScaleGridIndexer3() { CellSize = cellSize };
|
||||
_grid = new Dictionary<Vector3i, List<T>>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 格子大小(模型单位)
|
||||
/// </summary>
|
||||
public double CellSize => _indexer.CellSize;
|
||||
|
||||
/// <summary>
|
||||
/// 格子总数
|
||||
/// </summary>
|
||||
public int CellCount => _grid.Count;
|
||||
|
||||
/// <summary>
|
||||
/// 对象总数
|
||||
/// </summary>
|
||||
public int ObjectCount
|
||||
{
|
||||
get
|
||||
{
|
||||
int count = 0;
|
||||
foreach (var list in _grid.Values)
|
||||
{
|
||||
count += list.Count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插入对象到空间哈希网格
|
||||
/// </summary>
|
||||
/// <param name="item">要插入的对象</param>
|
||||
/// <param name="position">对象的3D位置</param>
|
||||
public void Insert(T item, Vector3d position)
|
||||
{
|
||||
var gridCell = _indexer.ToGrid(position);
|
||||
|
||||
if (!_grid.TryGetValue(gridCell, out var cellList))
|
||||
{
|
||||
cellList = new List<T>();
|
||||
_grid[gridCell] = cellList;
|
||||
}
|
||||
|
||||
cellList.Add(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量插入对象
|
||||
/// </summary>
|
||||
/// <param name="items">对象和位置的键值对</param>
|
||||
public void InsertRange(IEnumerable<KeyValuePair<T, Vector3d>> items)
|
||||
{
|
||||
foreach (var kvp in items)
|
||||
{
|
||||
Insert(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定格子中的所有对象
|
||||
/// </summary>
|
||||
/// <param name="gridCell">格子坐标</param>
|
||||
/// <returns>该格子中的对象列表</returns>
|
||||
public List<T> GetObjectsInCell(Vector3i gridCell)
|
||||
{
|
||||
if (_grid.TryGetValue(gridCell, out var list))
|
||||
{
|
||||
return new List<T>(list); // 返回副本,避免外部修改
|
||||
}
|
||||
return new List<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 范围查询:查找指定位置附近指定半径内的所有对象
|
||||
/// </summary>
|
||||
/// <param name="center">查询中心位置</param>
|
||||
/// <param name="radius">查询半径(模型单位)</param>
|
||||
/// <param name="distanceFunc">可选的距离计算函数(用于精确过滤)</param>
|
||||
/// <returns>范围内的对象列表(去重)</returns>
|
||||
public List<T> FindInRadius(
|
||||
Vector3d center,
|
||||
double radius,
|
||||
Func<T, double> distanceFunc = null)
|
||||
{
|
||||
var results = new HashSet<T>();
|
||||
|
||||
// 计算需要检查的格子范围
|
||||
int gridRadius = (int)Math.Ceiling(radius / _indexer.CellSize);
|
||||
var centerGrid = _indexer.ToGrid(center);
|
||||
|
||||
// 优化:提前创建忽略函数,避免在循环中检查 null(借鉴 PointHashGrid3d)
|
||||
bool hasDistanceCheck = (distanceFunc != null);
|
||||
|
||||
// 遍历查询范围内的所有格子
|
||||
for (int dx = -gridRadius; dx <= gridRadius; dx++)
|
||||
{
|
||||
for (int dy = -gridRadius; dy <= gridRadius; dy++)
|
||||
{
|
||||
for (int dz = -gridRadius; dz <= gridRadius; dz++)
|
||||
{
|
||||
var gridCell = new Vector3i(
|
||||
centerGrid.x + dx,
|
||||
centerGrid.y + dy,
|
||||
centerGrid.z + dz
|
||||
);
|
||||
|
||||
// 使用 TryGetValue 避免两次字典查找(借鉴 PointHashGrid3d)
|
||||
if (_grid.TryGetValue(gridCell, out var cellObjects))
|
||||
{
|
||||
foreach (var obj in cellObjects)
|
||||
{
|
||||
// 如果提供了距离函数,进行精确距离检查
|
||||
if (hasDistanceCheck)
|
||||
{
|
||||
double distance = distanceFunc(obj);
|
||||
if (distance <= radius)
|
||||
{
|
||||
results.Add(obj);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 否则直接添加(假设格子内的对象都在范围内)
|
||||
results.Add(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new List<T>(results);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 范围查询(带精确距离检查)
|
||||
/// </summary>
|
||||
/// <param name="center">查询中心</param>
|
||||
/// <param name="radius">查询半径</param>
|
||||
/// <param name="getPositionFunc">获取对象位置的函数</param>
|
||||
/// <returns>范围内的对象列表</returns>
|
||||
public List<T> FindInRadiusExact(
|
||||
Vector3d center,
|
||||
double radius,
|
||||
Func<T, Vector3d> getPositionFunc)
|
||||
{
|
||||
if (getPositionFunc == null)
|
||||
throw new ArgumentNullException(nameof(getPositionFunc));
|
||||
|
||||
return FindInRadius(center, radius, obj =>
|
||||
{
|
||||
var objPos = getPositionFunc(obj);
|
||||
return center.Distance(objPos);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空所有数据
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
_grid.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取网格统计信息(用于调试和性能分析)
|
||||
/// </summary>
|
||||
/// <returns>统计信息字符串</returns>
|
||||
public string GetStatistics()
|
||||
{
|
||||
int totalObjects = 0;
|
||||
int maxObjectsPerCell = 0;
|
||||
int minObjectsPerCell = int.MaxValue;
|
||||
int emptyCount = 0;
|
||||
|
||||
foreach (var list in _grid.Values)
|
||||
{
|
||||
int count = list.Count;
|
||||
totalObjects += count;
|
||||
|
||||
if (count == 0)
|
||||
emptyCount++;
|
||||
|
||||
if (count > maxObjectsPerCell)
|
||||
maxObjectsPerCell = count;
|
||||
|
||||
if (count < minObjectsPerCell && count > 0)
|
||||
minObjectsPerCell = count;
|
||||
}
|
||||
|
||||
double avgObjectsPerCell = _grid.Count > 0 ? (double)totalObjects / _grid.Count : 0;
|
||||
|
||||
if (minObjectsPerCell == int.MaxValue)
|
||||
minObjectsPerCell = 0;
|
||||
|
||||
return $"空间哈希网格统计:\n" +
|
||||
$" 格子大小: {_indexer.CellSize:F2} 模型单位\n" +
|
||||
$" 总格子数: {_grid.Count}\n" +
|
||||
$" 总对象数: {totalObjects}\n" +
|
||||
$" 平均每格对象数: {avgObjectsPerCell:F2}\n" +
|
||||
$" 最大每格对象数: {maxObjectsPerCell}\n" +
|
||||
$" 最小每格对象数: {minObjectsPerCell}\n" +
|
||||
$" 空格子数: {emptyCount}";
|
||||
}
|
||||
}
|
||||
}
|
||||
301
src/Core/Spatial/SpatialIndexManager.cs
Normal file
301
src/Core/Spatial/SpatialIndexManager.cs
Normal file
@ -0,0 +1,301 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Autodesk.Navisworks.Api;
|
||||
using NavisworksTransport.Utils;
|
||||
using g4; // geometry4Sharp
|
||||
|
||||
// 使用命名空间引用以避免类名冲突
|
||||
using ClashIntegration = NavisworksTransport.ClashDetectiveIntegration;
|
||||
|
||||
namespace NavisworksTransport.Core.Spatial
|
||||
{
|
||||
/// <summary>
|
||||
/// 全局空间索引管理器
|
||||
/// 单例模式,为所有动画提供高效的空间查询服务
|
||||
/// </summary>
|
||||
public class SpatialIndexManager
|
||||
{
|
||||
private static SpatialIndexManager _instance;
|
||||
private static readonly object _lockObj = new object();
|
||||
|
||||
/// <summary>
|
||||
/// 单例实例
|
||||
/// </summary>
|
||||
public static SpatialIndexManager Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
lock (_lockObj)
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = new SpatialIndexManager();
|
||||
}
|
||||
}
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
private SpatialHashGrid<ModelItem> _globalSpatialIndex;
|
||||
private double _cellSize = 1.0; // 默认格子大小(模型单位)
|
||||
private bool _isInitialized = false;
|
||||
private int _indexedObjectCount = 0;
|
||||
|
||||
// 缓存对象位置,避免重复计算
|
||||
private readonly Dictionary<ModelItem, Vector3d> _objectPositions = new Dictionary<ModelItem, Vector3d>();
|
||||
|
||||
private SpatialIndexManager()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 空间索引是否已初始化
|
||||
/// </summary>
|
||||
public bool IsInitialized => _isInitialized;
|
||||
|
||||
/// <summary>
|
||||
/// 格子大小(模型单位)
|
||||
/// </summary>
|
||||
public double CellSize => _cellSize;
|
||||
|
||||
/// <summary>
|
||||
/// 已索引的对象数量
|
||||
/// </summary>
|
||||
public int IndexedObjectCount => _indexedObjectCount;
|
||||
|
||||
/// <summary>
|
||||
/// 构建全局空间索引
|
||||
/// </summary>
|
||||
/// <param name="cellSizeInModelUnits">格子大小(模型单位),建议设置为车辆半径的2倍</param>
|
||||
public void BuildGlobalIndex(double cellSizeInModelUnits = 1.0)
|
||||
{
|
||||
var sw = Stopwatch.StartNew();
|
||||
LogManager.Info("[空间索引] 开始构建全局空间索引");
|
||||
LogManager.Info($"[空间索引] 格子大小: {cellSizeInModelUnits:F2} 模型单位");
|
||||
|
||||
try
|
||||
{
|
||||
_cellSize = cellSizeInModelUnits;
|
||||
|
||||
// 1. 直接从缓存获取已排除通道的几何对象(调用方已在动画生成阶段构建缓存)
|
||||
var nonChannelItems = ClashIntegration.GetNonChannelGeometryItemsCache();
|
||||
|
||||
if (nonChannelItems == null)
|
||||
{
|
||||
// 缓存不存在,说明调用方未按预期构建缓存,这是逻辑错误
|
||||
LogManager.Error("[空间索引] 几何对象缓存或通道缓存不存在!调用方应在动画生成阶段构建缓存。");
|
||||
throw new InvalidOperationException("空间索引构建失败:几何对象缓存未初始化。请先调用 BuildAllGeometryItemsCache() 和 BuildChannelObjectsCache()。");
|
||||
}
|
||||
|
||||
LogManager.Info($"[空间索引] 从缓存获取 {nonChannelItems.Count} 个非通道几何对象(已过滤通道)");
|
||||
|
||||
if (nonChannelItems.Count == 0)
|
||||
{
|
||||
LogManager.Warning("[空间索引] 场景中没有非通道几何对象");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 创建空间哈希网格
|
||||
_globalSpatialIndex = new SpatialHashGrid<ModelItem>(cellSizeInModelUnits);
|
||||
_objectPositions.Clear();
|
||||
|
||||
// 3. 索引所有非通道对象
|
||||
int indexedCount = 0;
|
||||
int failedCount = 0;
|
||||
|
||||
foreach (var item in nonChannelItems)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 获取包围盒中心作为对象位置
|
||||
var bbox = item.BoundingBox();
|
||||
var center = new Vector3d(
|
||||
(bbox.Min.X + bbox.Max.X) / 2.0,
|
||||
(bbox.Min.Y + bbox.Max.Y) / 2.0,
|
||||
(bbox.Min.Z + bbox.Max.Z) / 2.0
|
||||
);
|
||||
|
||||
// 插入到空间索引
|
||||
_globalSpatialIndex.Insert(item, center);
|
||||
|
||||
// 缓存位置
|
||||
_objectPositions[item] = center;
|
||||
|
||||
indexedCount++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Warning($"[空间索引] 索引对象失败: {item.DisplayName}, 错误: {ex.Message}");
|
||||
failedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
_indexedObjectCount = indexedCount;
|
||||
_isInitialized = true;
|
||||
|
||||
sw.Stop();
|
||||
|
||||
LogManager.Info("[空间索引] 构建完成");
|
||||
LogManager.Info($" - 成功索引: {indexedCount} 个对象(通道已在缓存阶段过滤)");
|
||||
LogManager.Info($" - 失败: {failedCount} 个对象");
|
||||
LogManager.Info($" - 格子数量: {_globalSpatialIndex.CellCount} 个");
|
||||
LogManager.Info($" - 耗时: {sw.ElapsedMilliseconds} ms");
|
||||
|
||||
// 输出统计信息
|
||||
LogManager.Debug("[空间索引] " + _globalSpatialIndex.GetStatistics());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"[空间索引] 构建失败: {ex.Message}");
|
||||
LogManager.Error($"[空间索引] 堆栈跟踪: {ex.StackTrace}");
|
||||
_isInitialized = false;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 范围查询:查找指定位置附近的对象
|
||||
/// </summary>
|
||||
/// <param name="position">查询中心位置(Navisworks Point3D)</param>
|
||||
/// <param name="searchRadiusInModelUnits">查询半径(模型单位)</param>
|
||||
/// <param name="excludeObject">要排除的对象(通常是移动物体本身)</param>
|
||||
/// <returns>范围内的对象列表</returns>
|
||||
public List<ModelItem> FindNearbyObjects(
|
||||
Point3D position,
|
||||
double searchRadiusInModelUnits,
|
||||
ModelItem excludeObject = null)
|
||||
{
|
||||
if (!_isInitialized)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"[空间索引] 空间索引未初始化,请先调用 BuildGlobalIndex()");
|
||||
}
|
||||
|
||||
var sw = Stopwatch.StartNew();
|
||||
|
||||
try
|
||||
{
|
||||
// 转换为 geometry4Sharp 的 Vector3d
|
||||
var centerVec = new Vector3d(position.X, position.Y, position.Z);
|
||||
|
||||
// 使用空间哈希网格进行范围查询(带精确距离检查)
|
||||
var nearbyObjects = _globalSpatialIndex.FindInRadiusExact(
|
||||
centerVec,
|
||||
searchRadiusInModelUnits,
|
||||
getPositionFunc: item =>
|
||||
{
|
||||
// 从缓存中获取对象位置
|
||||
if (_objectPositions.TryGetValue(item, out var pos))
|
||||
{
|
||||
return pos;
|
||||
}
|
||||
|
||||
// 缓存未命中,重新计算(理论上不应该发生)
|
||||
var bbox = item.BoundingBox();
|
||||
return new Vector3d(
|
||||
(bbox.Min.X + bbox.Max.X) / 2.0,
|
||||
(bbox.Min.Y + bbox.Max.Y) / 2.0,
|
||||
(bbox.Min.Z + bbox.Max.Z) / 2.0
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// 排除指定对象
|
||||
if (excludeObject != null)
|
||||
{
|
||||
nearbyObjects = nearbyObjects.Where(obj => !obj.Equals(excludeObject)).ToList();
|
||||
}
|
||||
|
||||
sw.Stop();
|
||||
|
||||
LogManager.Debug($"[空间索引] 范围查询完成: " +
|
||||
$"位置=({position.X:F2}, {position.Y:F2}, {position.Z:F2}), " +
|
||||
$"半径={searchRadiusInModelUnits:F2}, " +
|
||||
$"结果={nearbyObjects.Count} 个对象, " +
|
||||
$"耗时={sw.Elapsed.TotalMilliseconds:F2}ms");
|
||||
|
||||
return nearbyObjects;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"[空间索引] 范围查询失败: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 范围查询(带排除列表)
|
||||
/// </summary>
|
||||
/// <param name="position">查询中心位置</param>
|
||||
/// <param name="searchRadiusInModelUnits">查询半径</param>
|
||||
/// <param name="excludeObjects">要排除的对象列表</param>
|
||||
/// <returns>范围内的对象列表</returns>
|
||||
public List<ModelItem> FindNearbyObjects(
|
||||
Point3D position,
|
||||
double searchRadiusInModelUnits,
|
||||
IEnumerable<ModelItem> excludeObjects)
|
||||
{
|
||||
var results = FindNearbyObjects(position, searchRadiusInModelUnits, excludeObject: null);
|
||||
|
||||
if (excludeObjects != null)
|
||||
{
|
||||
var excludeSet = new HashSet<ModelItem>(excludeObjects);
|
||||
results = results.Where(obj => !excludeSet.Contains(obj)).ToList();
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取对象的缓存位置
|
||||
/// </summary>
|
||||
/// <param name="item">模型对象</param>
|
||||
/// <returns>对象的3D位置(Vector3d)</returns>
|
||||
public Vector3d? GetObjectPosition(ModelItem item)
|
||||
{
|
||||
if (_objectPositions.TryGetValue(item, out var pos))
|
||||
{
|
||||
return pos;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除空间索引
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
LogManager.Info("[空间索引] 清除空间索引");
|
||||
|
||||
_globalSpatialIndex?.Clear();
|
||||
_objectPositions.Clear();
|
||||
_isInitialized = false;
|
||||
_indexedObjectCount = 0;
|
||||
|
||||
LogManager.Info("[空间索引] 空间索引已清除");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取空间索引统计信息
|
||||
/// </summary>
|
||||
/// <returns>统计信息字符串</returns>
|
||||
public string GetStatistics()
|
||||
{
|
||||
if (!_isInitialized)
|
||||
{
|
||||
return "[空间索引] 未初始化";
|
||||
}
|
||||
|
||||
return $"[空间索引] 统计信息:\n" +
|
||||
$" - 已索引对象: {_indexedObjectCount} 个\n" +
|
||||
$" - 格子大小: {_cellSize:F2} 模型单位\n" +
|
||||
$" - 格子数量: {_globalSpatialIndex.CellCount} 个\n" +
|
||||
$"{_globalSpatialIndex.GetStatistics()}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -30,7 +30,6 @@ namespace NavisworksTransport.UI.WPF.Collections
|
||||
private readonly object _lock = new object();
|
||||
private readonly SynchronizationContext _synchronizationContext;
|
||||
private volatile bool _suppressNotification = false;
|
||||
private readonly DataBindingPerformanceMonitor _performanceMonitor;
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否正在批量更新中(禁用通知)
|
||||
@ -62,11 +61,10 @@ namespace NavisworksTransport.UI.WPF.Collections
|
||||
: base()
|
||||
{
|
||||
_synchronizationContext = SynchronizationContext.Current;
|
||||
_performanceMonitor = DataBindingPerformanceMonitor.Instance;
|
||||
|
||||
|
||||
// 启用WPF集合同步机制
|
||||
BindingOperations.EnableCollectionSynchronization(this, _lock);
|
||||
|
||||
|
||||
LogManager.Debug($"ThreadSafeObservableCollection<{typeof(T).Name}>已初始化");
|
||||
}
|
||||
|
||||
@ -78,11 +76,10 @@ namespace NavisworksTransport.UI.WPF.Collections
|
||||
: base(collection)
|
||||
{
|
||||
_synchronizationContext = SynchronizationContext.Current;
|
||||
_performanceMonitor = DataBindingPerformanceMonitor.Instance;
|
||||
|
||||
|
||||
// 启用WPF集合同步机制
|
||||
BindingOperations.EnableCollectionSynchronization(this, _lock);
|
||||
|
||||
|
||||
LogManager.Debug($"ThreadSafeObservableCollection<{typeof(T).Name}>已初始化,包含{Count}个元素");
|
||||
}
|
||||
|
||||
@ -206,36 +203,33 @@ namespace NavisworksTransport.UI.WPF.Collections
|
||||
return;
|
||||
}
|
||||
|
||||
using (_performanceMonitor.BeginCollectionUpdate(typeof(ThreadSafeObservableCollection<T>), "AddRange", itemList.Count))
|
||||
lock (_lock)
|
||||
{
|
||||
lock (_lock)
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
// 禁用通知
|
||||
_suppressNotification = true;
|
||||
// 禁用通知
|
||||
_suppressNotification = true;
|
||||
|
||||
// 批量添加
|
||||
foreach (var item in itemList)
|
||||
// 批量添加
|
||||
foreach (var item in itemList)
|
||||
{
|
||||
if (item != null)
|
||||
{
|
||||
if (item != null)
|
||||
{
|
||||
base.Add(item);
|
||||
}
|
||||
base.Add(item);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 恢复通知
|
||||
_suppressNotification = false;
|
||||
}
|
||||
|
||||
// 触发批量添加事件
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(
|
||||
NotifyCollectionChangedAction.Add,
|
||||
itemList,
|
||||
Count - itemList.Count));
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 恢复通知
|
||||
_suppressNotification = false;
|
||||
}
|
||||
|
||||
// 触发批量添加事件
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(
|
||||
NotifyCollectionChangedAction.Add,
|
||||
itemList,
|
||||
Count - itemList.Count));
|
||||
}
|
||||
|
||||
LogManager.Debug($"ThreadSafeObservableCollection批量添加{itemList.Count}个元素");
|
||||
|
||||
@ -25,9 +25,6 @@ namespace NavisworksTransport.UI.WPF.Services
|
||||
private readonly ConcurrentDictionary<string, CachedBindingValue> _bindingCache;
|
||||
private readonly ConcurrentDictionary<BindingExpression, BindingMetadata> _bindingMetadata;
|
||||
|
||||
// 性能监控
|
||||
private readonly DataBindingPerformanceMonitor _performanceMonitor;
|
||||
|
||||
// 优化配置
|
||||
private TimeSpan _cacheExpiration = TimeSpan.FromMinutes(5);
|
||||
private int _maxCacheSize = 1000;
|
||||
@ -125,7 +122,6 @@ namespace NavisworksTransport.UI.WPF.Services
|
||||
_bindingCache = new ConcurrentDictionary<string, CachedBindingValue>();
|
||||
_bindingMetadata = new ConcurrentDictionary<BindingExpression, BindingMetadata>();
|
||||
_pendingUpdates = new HashSet<BindingExpression>();
|
||||
_performanceMonitor = DataBindingPerformanceMonitor.Instance;
|
||||
|
||||
// 初始化批量更新定时器
|
||||
_batchUpdateTimer = new DispatcherTimer(DispatcherPriority.Background)
|
||||
@ -292,10 +288,7 @@ namespace NavisworksTransport.UI.WPF.Services
|
||||
|
||||
try
|
||||
{
|
||||
using (_performanceMonitor.BeginPropertyUpdate(bindingExpression.Target?.GetType() ?? typeof(object), GetBindingPath(bindingExpression)))
|
||||
{
|
||||
bindingExpression.UpdateTarget();
|
||||
}
|
||||
bindingExpression.UpdateTarget();
|
||||
|
||||
// 更新元数据
|
||||
if (_bindingMetadata.TryGetValue(bindingExpression, out var metadata))
|
||||
@ -336,16 +329,13 @@ namespace NavisworksTransport.UI.WPF.Services
|
||||
|
||||
try
|
||||
{
|
||||
using (_performanceMonitor.BeginCollectionUpdate(typeof(BindingExpressionOptimizer), "BatchUpdate", updatesToProcess.Count))
|
||||
{
|
||||
// 按优先级和类型分组
|
||||
var groupedUpdates = GroupUpdatesByPriority(updatesToProcess);
|
||||
// 按优先级和类型分组
|
||||
var groupedUpdates = GroupUpdatesByPriority(updatesToProcess);
|
||||
|
||||
// 按优先级顺序处理
|
||||
foreach (var group in groupedUpdates.OrderByDescending(g => g.Key))
|
||||
{
|
||||
ProcessUpdateGroup(group.Value);
|
||||
}
|
||||
// 按优先级顺序处理
|
||||
foreach (var group in groupedUpdates.OrderByDescending(g => g.Key))
|
||||
{
|
||||
ProcessUpdateGroup(group.Value);
|
||||
}
|
||||
|
||||
LogManager.Debug($"批量更新完成: {updatesToProcess.Count}个绑定");
|
||||
|
||||
@ -1,695 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace NavisworksTransport.UI.WPF.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据绑定性能监控器 - 监控和分析WPF数据绑定的性能表现
|
||||
/// 提供详细的性能指标收集和优化建议
|
||||
/// </summary>
|
||||
public class DataBindingPerformanceMonitor : IDisposable
|
||||
{
|
||||
#region 字段和属性
|
||||
|
||||
private static DataBindingPerformanceMonitor _instance;
|
||||
private static readonly object _instanceLock = new object();
|
||||
|
||||
// 性能数据收集
|
||||
private readonly ConcurrentDictionary<string, PropertyUpdateMetrics> _propertyMetrics;
|
||||
private readonly ConcurrentDictionary<string, CollectionUpdateMetrics> _collectionMetrics;
|
||||
private readonly ConcurrentQueue<BindingUpdateEvent> _updateEvents;
|
||||
private readonly Timer _reportTimer;
|
||||
private volatile bool _isDisposed = false;
|
||||
private volatile bool _isMonitoringEnabled = true;
|
||||
|
||||
// 监控配置
|
||||
private TimeSpan _reportInterval = TimeSpan.FromMinutes(5);
|
||||
private int _maxEventHistory = 10000;
|
||||
private int _performanceThresholdMs = 16; // 60FPS下每帧约16ms
|
||||
|
||||
/// <summary>
|
||||
/// 获取性能监控器的单例实例
|
||||
/// </summary>
|
||||
public static DataBindingPerformanceMonitor Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
lock (_instanceLock)
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = new DataBindingPerformanceMonitor();
|
||||
}
|
||||
}
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用性能监控
|
||||
/// </summary>
|
||||
public bool IsMonitoringEnabled
|
||||
{
|
||||
get => _isMonitoringEnabled;
|
||||
set => _isMonitoringEnabled = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 性能阈值(毫秒)- 超过此值的操作将被标记为慢操作
|
||||
/// </summary>
|
||||
public int PerformanceThresholdMs
|
||||
{
|
||||
get => _performanceThresholdMs;
|
||||
set => _performanceThresholdMs = Math.Max(1, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前监控的属性数量
|
||||
/// </summary>
|
||||
public int MonitoredPropertiesCount => _propertyMetrics.Count;
|
||||
|
||||
/// <summary>
|
||||
/// 当前监控的集合数量
|
||||
/// </summary>
|
||||
public int MonitoredCollectionsCount => _collectionMetrics.Count;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 构造函数和初始化
|
||||
|
||||
private DataBindingPerformanceMonitor()
|
||||
{
|
||||
_propertyMetrics = new ConcurrentDictionary<string, PropertyUpdateMetrics>();
|
||||
_collectionMetrics = new ConcurrentDictionary<string, CollectionUpdateMetrics>();
|
||||
_updateEvents = new ConcurrentQueue<BindingUpdateEvent>();
|
||||
|
||||
// 定期生成性能报告
|
||||
_reportTimer = new Timer(GeneratePerformanceReport, null, _reportInterval, _reportInterval);
|
||||
|
||||
LogManager.Info("数据绑定性能监控器已初始化");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 核心监控接口
|
||||
|
||||
/// <summary>
|
||||
/// 记录属性变更事件的开始
|
||||
/// </summary>
|
||||
/// <param name="viewModelType">ViewModel类型</param>
|
||||
/// <param name="propertyName">属性名称</param>
|
||||
/// <returns>性能测量上下文</returns>
|
||||
public IDisposable BeginPropertyUpdate(Type viewModelType, string propertyName)
|
||||
{
|
||||
if (!_isMonitoringEnabled || _isDisposed)
|
||||
return new EmptyDisposable();
|
||||
|
||||
var key = $"{viewModelType.Name}.{propertyName}";
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
|
||||
return new PropertyUpdateContext(this, key, stopwatch, Dispatcher.CurrentDispatcher);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 记录集合变更事件的开始
|
||||
/// </summary>
|
||||
/// <param name="collectionType">集合类型</param>
|
||||
/// <param name="operationType">操作类型(Add, Remove, Clear等)</param>
|
||||
/// <param name="itemCount">影响的项目数量</param>
|
||||
/// <returns>性能测量上下文</returns>
|
||||
public IDisposable BeginCollectionUpdate(Type collectionType, string operationType, int itemCount = 1)
|
||||
{
|
||||
if (!_isMonitoringEnabled || _isDisposed)
|
||||
return new EmptyDisposable();
|
||||
|
||||
var key = $"{collectionType.Name}.{operationType}";
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
|
||||
return new CollectionUpdateContext(this, key, stopwatch, itemCount, Dispatcher.CurrentDispatcher);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 记录绑定更新事件
|
||||
/// </summary>
|
||||
/// <param name="bindingPath">绑定路径</param>
|
||||
/// <param name="updateType">更新类型</param>
|
||||
/// <param name="durationMs">更新耗时(毫秒)</param>
|
||||
public void RecordBindingUpdate(string bindingPath, string updateType, double durationMs)
|
||||
{
|
||||
if (!_isMonitoringEnabled || _isDisposed)
|
||||
return;
|
||||
|
||||
var updateEvent = new BindingUpdateEvent
|
||||
{
|
||||
Timestamp = DateTime.UtcNow,
|
||||
BindingPath = bindingPath,
|
||||
UpdateType = updateType,
|
||||
DurationMs = durationMs,
|
||||
IsSlowUpdate = durationMs > _performanceThresholdMs,
|
||||
ThreadId = Thread.CurrentThread.ManagedThreadId
|
||||
};
|
||||
|
||||
_updateEvents.Enqueue(updateEvent);
|
||||
|
||||
// 限制事件历史记录数量
|
||||
while (_updateEvents.Count > _maxEventHistory)
|
||||
{
|
||||
_updateEvents.TryDequeue(out _);
|
||||
}
|
||||
|
||||
// 记录慢更新警告
|
||||
if (updateEvent.IsSlowUpdate)
|
||||
{
|
||||
LogManager.Warning($"检测到慢数据绑定更新: {bindingPath} ({updateType}) - {durationMs:F2}ms");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 内部方法
|
||||
|
||||
/// <summary>
|
||||
/// 完成属性更新监控
|
||||
/// </summary>
|
||||
internal void EndPropertyUpdate(string key, Stopwatch stopwatch, Dispatcher dispatcher)
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
|
||||
stopwatch.Stop();
|
||||
var durationMs = stopwatch.Elapsed.TotalMilliseconds;
|
||||
|
||||
var metrics = _propertyMetrics.GetOrAdd(key, _ => new PropertyUpdateMetrics(key));
|
||||
metrics.RecordUpdate(durationMs, dispatcher == Dispatcher.CurrentDispatcher);
|
||||
|
||||
RecordBindingUpdate(key, "PropertyChanged", durationMs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 完成集合更新监控
|
||||
/// </summary>
|
||||
internal void EndCollectionUpdate(string key, Stopwatch stopwatch, int itemCount, Dispatcher dispatcher)
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
|
||||
stopwatch.Stop();
|
||||
var durationMs = stopwatch.Elapsed.TotalMilliseconds;
|
||||
|
||||
var metrics = _collectionMetrics.GetOrAdd(key, _ => new CollectionUpdateMetrics(key));
|
||||
metrics.RecordUpdate(durationMs, itemCount, dispatcher == Dispatcher.CurrentDispatcher);
|
||||
|
||||
RecordBindingUpdate(key, "CollectionChanged", durationMs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成性能报告
|
||||
/// </summary>
|
||||
private void GeneratePerformanceReport(object state)
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
|
||||
try
|
||||
{
|
||||
var report = GenerateDetailedReport();
|
||||
LogManager.Info($"数据绑定性能报告:\n{report}");
|
||||
|
||||
// 清理过期数据
|
||||
CleanupExpiredData();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"生成数据绑定性能报告失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理过期数据
|
||||
/// </summary>
|
||||
private void CleanupExpiredData()
|
||||
{
|
||||
var cutoffTime = DateTime.UtcNow.Subtract(TimeSpan.FromHours(1));
|
||||
|
||||
// 清理属性指标中的过期数据
|
||||
foreach (var metrics in _propertyMetrics.Values)
|
||||
{
|
||||
metrics.CleanupOldData(cutoffTime);
|
||||
}
|
||||
|
||||
// 清理集合指标中的过期数据
|
||||
foreach (var metrics in _collectionMetrics.Values)
|
||||
{
|
||||
metrics.CleanupOldData(cutoffTime);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 报告生成
|
||||
|
||||
/// <summary>
|
||||
/// 生成详细的性能报告
|
||||
/// </summary>
|
||||
/// <returns>性能报告字符串</returns>
|
||||
public string GenerateDetailedReport()
|
||||
{
|
||||
var report = new System.Text.StringBuilder();
|
||||
|
||||
report.AppendLine("=== 数据绑定性能监控报告 ===");
|
||||
report.AppendLine($"报告时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
|
||||
report.AppendLine($"监控状态: {(IsMonitoringEnabled ? "启用" : "禁用")}");
|
||||
report.AppendLine($"性能阈值: {PerformanceThresholdMs}ms");
|
||||
report.AppendLine();
|
||||
|
||||
// 属性更新统计
|
||||
report.AppendLine("属性更新性能统计:");
|
||||
var topSlowProperties = _propertyMetrics.Values
|
||||
.Where(m => m.UpdateCount > 0)
|
||||
.OrderByDescending(m => m.AverageUpdateTime)
|
||||
.Take(10)
|
||||
.ToList();
|
||||
|
||||
if (topSlowProperties.Any())
|
||||
{
|
||||
foreach (var metrics in topSlowProperties)
|
||||
{
|
||||
report.AppendLine($" {metrics.PropertyKey}:");
|
||||
report.AppendLine($" 更新次数: {metrics.UpdateCount}");
|
||||
report.AppendLine($" 平均耗时: {metrics.AverageUpdateTime:F2}ms");
|
||||
report.AppendLine($" 最大耗时: {metrics.MaxUpdateTime:F2}ms");
|
||||
report.AppendLine($" 慢更新次数: {metrics.SlowUpdateCount}");
|
||||
report.AppendLine($" 跨线程更新次数: {metrics.CrossThreadUpdateCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
report.AppendLine(" 暂无属性更新数据");
|
||||
}
|
||||
|
||||
report.AppendLine();
|
||||
|
||||
// 集合更新统计
|
||||
report.AppendLine("集合更新性能统计:");
|
||||
var topSlowCollections = _collectionMetrics.Values
|
||||
.Where(m => m.UpdateCount > 0)
|
||||
.OrderByDescending(m => m.AverageUpdateTime)
|
||||
.Take(10)
|
||||
.ToList();
|
||||
|
||||
if (topSlowCollections.Any())
|
||||
{
|
||||
foreach (var metrics in topSlowCollections)
|
||||
{
|
||||
report.AppendLine($" {metrics.CollectionKey}:");
|
||||
report.AppendLine($" 更新次数: {metrics.UpdateCount}");
|
||||
report.AppendLine($" 平均耗时: {metrics.AverageUpdateTime:F2}ms");
|
||||
report.AppendLine($" 最大耗时: {metrics.MaxUpdateTime:F2}ms");
|
||||
report.AppendLine($" 处理项目总数: {metrics.TotalItemsProcessed}");
|
||||
report.AppendLine($" 慢更新次数: {metrics.SlowUpdateCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
report.AppendLine(" 暂无集合更新数据");
|
||||
}
|
||||
|
||||
report.AppendLine();
|
||||
|
||||
// 最近的慢更新事件
|
||||
var recentSlowUpdates = _updateEvents
|
||||
.Where(e => e.IsSlowUpdate && e.Timestamp > DateTime.UtcNow.Subtract(TimeSpan.FromMinutes(10)))
|
||||
.OrderByDescending(e => e.DurationMs)
|
||||
.Take(5)
|
||||
.ToList();
|
||||
|
||||
if (recentSlowUpdates.Any())
|
||||
{
|
||||
report.AppendLine("最近的慢更新事件 (10分钟内):");
|
||||
foreach (var evt in recentSlowUpdates)
|
||||
{
|
||||
report.AppendLine($" {evt.BindingPath} ({evt.UpdateType}) - {evt.DurationMs:F2}ms @ {evt.Timestamp:HH:mm:ss}");
|
||||
}
|
||||
}
|
||||
|
||||
// 性能优化建议
|
||||
report.AppendLine();
|
||||
report.AppendLine("性能优化建议:");
|
||||
|
||||
var suggestions = GenerateOptimizationSuggestions();
|
||||
if (suggestions.Any())
|
||||
{
|
||||
foreach (var suggestion in suggestions)
|
||||
{
|
||||
report.AppendLine($" • {suggestion}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
report.AppendLine(" 当前性能表现良好,暂无优化建议");
|
||||
}
|
||||
|
||||
return report.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成性能优化建议
|
||||
/// </summary>
|
||||
/// <returns>优化建议列表</returns>
|
||||
public List<string> GenerateOptimizationSuggestions()
|
||||
{
|
||||
var suggestions = new List<string>();
|
||||
|
||||
// 检查频繁更新的属性
|
||||
var frequentProperties = _propertyMetrics.Values
|
||||
.Where(m => m.UpdateCount > 100 && m.AverageUpdateTime > _performanceThresholdMs)
|
||||
.ToList();
|
||||
|
||||
if (frequentProperties.Any())
|
||||
{
|
||||
suggestions.Add($"发现{frequentProperties.Count}个高频慢更新属性,建议考虑使用延迟更新或批量更新机制");
|
||||
}
|
||||
|
||||
// 检查大集合操作
|
||||
var largeCollectionUpdates = _collectionMetrics.Values
|
||||
.Where(m => m.AverageItemsPerUpdate > 1000 && m.AverageUpdateTime > _performanceThresholdMs * 5)
|
||||
.ToList();
|
||||
|
||||
if (largeCollectionUpdates.Any())
|
||||
{
|
||||
suggestions.Add($"发现{largeCollectionUpdates.Count}个大集合更新操作,建议实现虚拟化或分页机制");
|
||||
}
|
||||
|
||||
// 检查跨线程更新
|
||||
var crossThreadUpdates = _propertyMetrics.Values
|
||||
.Where(m => m.CrossThreadUpdateCount > m.UpdateCount * 0.1) // 超过10%的跨线程更新
|
||||
.ToList();
|
||||
|
||||
if (crossThreadUpdates.Any())
|
||||
{
|
||||
suggestions.Add($"发现{crossThreadUpdates.Count}个属性存在频繁的跨线程更新,建议优化线程调度");
|
||||
}
|
||||
|
||||
// 检查内存使用
|
||||
var totalMetrics = _propertyMetrics.Count + _collectionMetrics.Count;
|
||||
if (totalMetrics > 1000)
|
||||
{
|
||||
suggestions.Add("监控的绑定数量较多,建议定期清理不再使用的绑定以释放内存");
|
||||
}
|
||||
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取性能概览数据
|
||||
/// </summary>
|
||||
/// <returns>性能概览</returns>
|
||||
public PerformanceOverview GetPerformanceOverview()
|
||||
{
|
||||
var totalPropertyUpdates = _propertyMetrics.Values.Sum(m => m.UpdateCount);
|
||||
var totalCollectionUpdates = _collectionMetrics.Values.Sum(m => m.UpdateCount);
|
||||
var totalSlowUpdates = _updateEvents.Count(e => e.IsSlowUpdate);
|
||||
|
||||
var avgPropertyUpdateTime = _propertyMetrics.Values
|
||||
.Where(m => m.UpdateCount > 0)
|
||||
.DefaultIfEmpty(new PropertyUpdateMetrics("dummy"))
|
||||
.Average(m => m.AverageUpdateTime);
|
||||
|
||||
var avgCollectionUpdateTime = _collectionMetrics.Values
|
||||
.Where(m => m.UpdateCount > 0)
|
||||
.DefaultIfEmpty(new CollectionUpdateMetrics("dummy"))
|
||||
.Average(m => m.AverageUpdateTime);
|
||||
|
||||
return new PerformanceOverview
|
||||
{
|
||||
TotalPropertyUpdates = totalPropertyUpdates,
|
||||
TotalCollectionUpdates = totalCollectionUpdates,
|
||||
TotalSlowUpdates = totalSlowUpdates,
|
||||
AveragePropertyUpdateTime = avgPropertyUpdateTime,
|
||||
AverageCollectionUpdateTime = avgCollectionUpdateTime,
|
||||
MonitoredPropertiesCount = MonitoredPropertiesCount,
|
||||
MonitoredCollectionsCount = MonitoredCollectionsCount,
|
||||
IsHealthy = totalSlowUpdates < (totalPropertyUpdates + totalCollectionUpdates) * 0.05 // 少于5%的慢更新
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 配置管理
|
||||
|
||||
/// <summary>
|
||||
/// 设置监控配置
|
||||
/// </summary>
|
||||
/// <param name="reportInterval">报告生成间隔</param>
|
||||
/// <param name="maxEventHistory">最大事件历史记录数</param>
|
||||
/// <param name="performanceThreshold">性能阈值(毫秒)</param>
|
||||
public void ConfigureMonitoring(TimeSpan reportInterval, int maxEventHistory, int performanceThreshold)
|
||||
{
|
||||
_reportInterval = reportInterval;
|
||||
_maxEventHistory = Math.Max(100, maxEventHistory);
|
||||
_performanceThresholdMs = Math.Max(1, performanceThreshold);
|
||||
|
||||
// 重启定时器
|
||||
_reportTimer?.Change(_reportInterval, _reportInterval);
|
||||
|
||||
LogManager.Info($"数据绑定监控配置已更新 - 报告间隔: {reportInterval}, 事件历史: {maxEventHistory}, 性能阈值: {performanceThreshold}ms");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重置所有监控数据
|
||||
/// </summary>
|
||||
public void ResetMonitoringData()
|
||||
{
|
||||
_propertyMetrics.Clear();
|
||||
_collectionMetrics.Clear();
|
||||
|
||||
while (_updateEvents.TryDequeue(out _)) { }
|
||||
|
||||
LogManager.Info("数据绑定监控数据已重置");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable实现
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
|
||||
_isDisposed = true;
|
||||
_isMonitoringEnabled = false;
|
||||
|
||||
_reportTimer?.Dispose();
|
||||
|
||||
// 生成最终报告
|
||||
try
|
||||
{
|
||||
var finalReport = GenerateDetailedReport();
|
||||
LogManager.Info($"数据绑定性能监控最终报告:\n{finalReport}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"生成最终性能报告失败: {ex.Message}");
|
||||
}
|
||||
|
||||
_propertyMetrics.Clear();
|
||||
_collectionMetrics.Clear();
|
||||
|
||||
LogManager.Info("数据绑定性能监控器已释放");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#region 辅助类和数据结构
|
||||
|
||||
/// <summary>
|
||||
/// 属性更新上下文 - 用于测量属性更新性能
|
||||
/// </summary>
|
||||
internal class PropertyUpdateContext : IDisposable
|
||||
{
|
||||
private readonly DataBindingPerformanceMonitor _monitor;
|
||||
private readonly string _key;
|
||||
private readonly Stopwatch _stopwatch;
|
||||
private readonly Dispatcher _dispatcher;
|
||||
|
||||
public PropertyUpdateContext(DataBindingPerformanceMonitor monitor, string key, Stopwatch stopwatch, Dispatcher dispatcher)
|
||||
{
|
||||
_monitor = monitor;
|
||||
_key = key;
|
||||
_stopwatch = stopwatch;
|
||||
_dispatcher = dispatcher;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_monitor.EndPropertyUpdate(_key, _stopwatch, _dispatcher);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 集合更新上下文 - 用于测量集合更新性能
|
||||
/// </summary>
|
||||
internal class CollectionUpdateContext : IDisposable
|
||||
{
|
||||
private readonly DataBindingPerformanceMonitor _monitor;
|
||||
private readonly string _key;
|
||||
private readonly Stopwatch _stopwatch;
|
||||
private readonly int _itemCount;
|
||||
private readonly Dispatcher _dispatcher;
|
||||
|
||||
public CollectionUpdateContext(DataBindingPerformanceMonitor monitor, string key, Stopwatch stopwatch, int itemCount, Dispatcher dispatcher)
|
||||
{
|
||||
_monitor = monitor;
|
||||
_key = key;
|
||||
_stopwatch = stopwatch;
|
||||
_itemCount = itemCount;
|
||||
_dispatcher = dispatcher;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_monitor.EndCollectionUpdate(_key, _stopwatch, _itemCount, _dispatcher);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 空的IDisposable实现 - 用于禁用监控时的占位符
|
||||
/// </summary>
|
||||
internal class EmptyDisposable : IDisposable
|
||||
{
|
||||
public void Dispose() { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 属性更新性能指标
|
||||
/// </summary>
|
||||
public class PropertyUpdateMetrics
|
||||
{
|
||||
public string PropertyKey { get; }
|
||||
public long UpdateCount { get; private set; }
|
||||
public double TotalUpdateTime { get; private set; }
|
||||
public double MaxUpdateTime { get; private set; }
|
||||
public long SlowUpdateCount { get; private set; }
|
||||
public long CrossThreadUpdateCount { get; private set; }
|
||||
public DateTime LastUpdateTime { get; private set; }
|
||||
|
||||
public double AverageUpdateTime => UpdateCount > 0 ? TotalUpdateTime / UpdateCount : 0;
|
||||
|
||||
private readonly List<DateTime> _updateTimes = new List<DateTime>();
|
||||
|
||||
public PropertyUpdateMetrics(string propertyKey)
|
||||
{
|
||||
PropertyKey = propertyKey;
|
||||
LastUpdateTime = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public void RecordUpdate(double durationMs, bool isOnUIThread)
|
||||
{
|
||||
UpdateCount++;
|
||||
TotalUpdateTime += durationMs;
|
||||
MaxUpdateTime = Math.Max(MaxUpdateTime, durationMs);
|
||||
LastUpdateTime = DateTime.UtcNow;
|
||||
|
||||
if (durationMs > 16) // 60FPS阈值
|
||||
{
|
||||
SlowUpdateCount++;
|
||||
}
|
||||
|
||||
if (!isOnUIThread)
|
||||
{
|
||||
CrossThreadUpdateCount++;
|
||||
}
|
||||
|
||||
_updateTimes.Add(DateTime.UtcNow);
|
||||
}
|
||||
|
||||
public void CleanupOldData(DateTime cutoffTime)
|
||||
{
|
||||
_updateTimes.RemoveAll(t => t < cutoffTime);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 集合更新性能指标
|
||||
/// </summary>
|
||||
public class CollectionUpdateMetrics
|
||||
{
|
||||
public string CollectionKey { get; }
|
||||
public long UpdateCount { get; private set; }
|
||||
public double TotalUpdateTime { get; private set; }
|
||||
public double MaxUpdateTime { get; private set; }
|
||||
public long TotalItemsProcessed { get; private set; }
|
||||
public long SlowUpdateCount { get; private set; }
|
||||
public DateTime LastUpdateTime { get; private set; }
|
||||
|
||||
public double AverageUpdateTime => UpdateCount > 0 ? TotalUpdateTime / UpdateCount : 0;
|
||||
public double AverageItemsPerUpdate => UpdateCount > 0 ? (double)TotalItemsProcessed / UpdateCount : 0;
|
||||
|
||||
private readonly List<DateTime> _updateTimes = new List<DateTime>();
|
||||
|
||||
public CollectionUpdateMetrics(string collectionKey)
|
||||
{
|
||||
CollectionKey = collectionKey;
|
||||
LastUpdateTime = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public void RecordUpdate(double durationMs, int itemCount, bool isOnUIThread)
|
||||
{
|
||||
UpdateCount++;
|
||||
TotalUpdateTime += durationMs;
|
||||
MaxUpdateTime = Math.Max(MaxUpdateTime, durationMs);
|
||||
TotalItemsProcessed += itemCount;
|
||||
LastUpdateTime = DateTime.UtcNow;
|
||||
|
||||
if (durationMs > 50) // 集合操作的更宽松阈值
|
||||
{
|
||||
SlowUpdateCount++;
|
||||
}
|
||||
|
||||
_updateTimes.Add(DateTime.UtcNow);
|
||||
}
|
||||
|
||||
public void CleanupOldData(DateTime cutoffTime)
|
||||
{
|
||||
_updateTimes.RemoveAll(t => t < cutoffTime);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 绑定更新事件
|
||||
/// </summary>
|
||||
public class BindingUpdateEvent
|
||||
{
|
||||
public DateTime Timestamp { get; set; }
|
||||
public string BindingPath { get; set; }
|
||||
public string UpdateType { get; set; }
|
||||
public double DurationMs { get; set; }
|
||||
public bool IsSlowUpdate { get; set; }
|
||||
public int ThreadId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 性能概览数据
|
||||
/// </summary>
|
||||
public class PerformanceOverview
|
||||
{
|
||||
public long TotalPropertyUpdates { get; set; }
|
||||
public long TotalCollectionUpdates { get; set; }
|
||||
public long TotalSlowUpdates { get; set; }
|
||||
public double AveragePropertyUpdateTime { get; set; }
|
||||
public double AverageCollectionUpdateTime { get; set; }
|
||||
public int MonitoredPropertiesCount { get; set; }
|
||||
public int MonitoredCollectionsCount { get; set; }
|
||||
public bool IsHealthy { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -35,9 +35,6 @@ namespace NavisworksTransport.UI.WPF.Services
|
||||
private readonly Dictionary<string, ConditionalUpdateConfig> _conditionalConfigs;
|
||||
private readonly object _conditionalConfigLock = new object();
|
||||
|
||||
// 性能监控集成
|
||||
private readonly DataBindingPerformanceMonitor _performanceMonitor;
|
||||
|
||||
// 配置选项
|
||||
private TimeSpan _defaultDelayInterval = TimeSpan.FromMilliseconds(100);
|
||||
private TimeSpan _batchUpdateInterval = TimeSpan.FromMilliseconds(50);
|
||||
@ -102,8 +99,6 @@ namespace NavisworksTransport.UI.WPF.Services
|
||||
_batchUpdates = new Dictionary<object, BatchUpdateContext>();
|
||||
_conditionalConfigs = new Dictionary<string, ConditionalUpdateConfig>();
|
||||
|
||||
_performanceMonitor = DataBindingPerformanceMonitor.Instance;
|
||||
|
||||
// 初始化延迟更新定时器
|
||||
_delayedUpdateTimer = new Timer(ProcessDelayedUpdates, null, _defaultDelayInterval, _defaultDelayInterval);
|
||||
|
||||
@ -241,10 +236,7 @@ namespace NavisworksTransport.UI.WPF.Services
|
||||
if (target == null)
|
||||
return;
|
||||
|
||||
using (_performanceMonitor.BeginPropertyUpdate(target.GetType(), context.PropertyName))
|
||||
{
|
||||
context.UpdateAction?.Invoke();
|
||||
}
|
||||
context.UpdateAction?.Invoke();
|
||||
|
||||
LogManager.Debug($"执行延迟更新: {context.PropertyName}, 合并次数: {context.UpdateCount}");
|
||||
}
|
||||
@ -436,22 +428,19 @@ namespace NavisworksTransport.UI.WPF.Services
|
||||
{
|
||||
var duration = DateTime.UtcNow - context.StartTime;
|
||||
|
||||
using (_performanceMonitor.BeginPropertyUpdate(target.GetType(), $"BatchUpdate_{properties.Count}Properties"))
|
||||
// 如果目标实现了批量更新接口,使用其批量方法
|
||||
if (target is ViewModelBase viewModelBase)
|
||||
{
|
||||
// 如果目标实现了批量更新接口,使用其批量方法
|
||||
if (target is ViewModelBase viewModelBase)
|
||||
viewModelBase.OnPropertiesChanged(properties.ToArray());
|
||||
}
|
||||
else
|
||||
{
|
||||
// 否则逐个触发属性变更通知
|
||||
foreach (var propertyName in properties)
|
||||
{
|
||||
viewModelBase.OnPropertiesChanged(properties.ToArray());
|
||||
}
|
||||
else
|
||||
{
|
||||
// 否则逐个触发属性变更通知
|
||||
foreach (var propertyName in properties)
|
||||
{
|
||||
// 使用反射或其他方式触发PropertyChanged事件
|
||||
// 这里需要根据具体的INotifyPropertyChanged实现来调用
|
||||
TriggerPropertyChanged(target, propertyName);
|
||||
}
|
||||
// 使用反射或其他方式触发PropertyChanged事件
|
||||
// 这里需要根据具体的INotifyPropertyChanged实现来调用
|
||||
TriggerPropertyChanged(target, propertyName);
|
||||
}
|
||||
}
|
||||
|
||||
@ -606,10 +595,7 @@ namespace NavisworksTransport.UI.WPF.Services
|
||||
try
|
||||
{
|
||||
// 执行延迟的更新
|
||||
using (_performanceMonitor.BeginPropertyUpdate(target.GetType(), propertyName))
|
||||
{
|
||||
TriggerPropertyChanged(target, propertyName);
|
||||
}
|
||||
TriggerPropertyChanged(target, propertyName);
|
||||
|
||||
lock (_conditionalConfigLock)
|
||||
{
|
||||
@ -680,16 +666,6 @@ namespace NavisworksTransport.UI.WPF.Services
|
||||
|
||||
report.AppendLine("条件更新机制:");
|
||||
report.AppendLine($" 配置的条件更新: {stats.ConfiguredConditionalUpdates}");
|
||||
report.AppendLine();
|
||||
|
||||
// 集成性能监控数据
|
||||
var performanceOverview = _performanceMonitor.GetPerformanceOverview();
|
||||
report.AppendLine("性能概览:");
|
||||
report.AppendLine($" 总属性更新: {performanceOverview.TotalPropertyUpdates}");
|
||||
report.AppendLine($" 总集合更新: {performanceOverview.TotalCollectionUpdates}");
|
||||
report.AppendLine($" 慢更新次数: {performanceOverview.TotalSlowUpdates}");
|
||||
report.AppendLine($" 平均属性更新时间: {performanceOverview.AveragePropertyUpdateTime:F2}ms");
|
||||
report.AppendLine($" 系统健康状态: {(performanceOverview.IsHealthy ? "良好" : "需要优化")}");
|
||||
|
||||
return report.ToString();
|
||||
}
|
||||
|
||||
@ -32,11 +32,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
/// </summary>
|
||||
private readonly SmartDataBindingOptimizer _bindingOptimizer;
|
||||
|
||||
/// <summary>
|
||||
/// 数据绑定性能监控器,监控绑定性能
|
||||
/// </summary>
|
||||
private readonly DataBindingPerformanceMonitor _performanceMonitor;
|
||||
|
||||
/// <summary>
|
||||
/// 正在更新的属性集合,用于防重入检测
|
||||
/// </summary>
|
||||
@ -52,11 +47,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
/// </summary>
|
||||
protected bool IsSmartOptimizationEnabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用性能监控
|
||||
/// </summary>
|
||||
protected bool IsPerformanceMonitoringEnabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 主ViewModel引用,用于统一状态栏更新
|
||||
/// </summary>
|
||||
@ -73,7 +63,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
{
|
||||
_uiStateManager = UIStateManager.Instance;
|
||||
_bindingOptimizer = SmartDataBindingOptimizer.Instance;
|
||||
_performanceMonitor = DataBindingPerformanceMonitor.Instance;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -81,7 +70,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
#region 属性更新方法
|
||||
|
||||
/// <summary>
|
||||
/// 触发属性变更通知(线程安全,防重入,集成性能监控)
|
||||
/// 触发属性变更通知(线程安全,防重入)
|
||||
/// </summary>
|
||||
/// <param name="propertyName">属性名称,自动获取调用者名称</param>
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
@ -91,13 +80,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
return;
|
||||
}
|
||||
|
||||
// 性能监控
|
||||
IDisposable performanceContext = null;
|
||||
if (IsPerformanceMonitoringEnabled)
|
||||
{
|
||||
performanceContext = _performanceMonitor.BeginPropertyUpdate(this.GetType(), propertyName);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 防重入检查
|
||||
@ -109,7 +91,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
LogManager.Debug($"[ViewModel] 检测到属性重入更新,跳过: {propertyName}");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 标记属性为正在更新状态
|
||||
_updatingProperties.Add(propertyName);
|
||||
}
|
||||
@ -140,9 +122,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
{
|
||||
_updatingProperties.Remove(propertyName);
|
||||
}
|
||||
|
||||
// 结束性能监控
|
||||
performanceContext?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@ -510,37 +489,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
|
||||
#endregion
|
||||
|
||||
#region 性能监控和诊断
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前ViewModel的绑定性能统计
|
||||
/// </summary>
|
||||
/// <returns>性能统计信息</returns>
|
||||
protected virtual string GetBindingPerformanceReport()
|
||||
{
|
||||
if (!IsPerformanceMonitoringEnabled)
|
||||
return "性能监控已禁用";
|
||||
|
||||
try
|
||||
{
|
||||
return _performanceMonitor.GenerateDetailedReport();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"[ViewModel] 生成性能报告失败: {ex.Message}");
|
||||
return $"生成性能报告失败: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启用或禁用性能监控
|
||||
/// </summary>
|
||||
/// <param name="enabled">是否启用</param>
|
||||
protected void SetPerformanceMonitoring(bool enabled)
|
||||
{
|
||||
IsPerformanceMonitoringEnabled = enabled;
|
||||
_performanceMonitor.IsMonitoringEnabled = enabled;
|
||||
}
|
||||
#region 诊断方法
|
||||
|
||||
/// <summary>
|
||||
/// 启用或禁用智能优化
|
||||
|
||||
@ -264,7 +264,7 @@ NavisworksTransport 碰撞检测报告对话框 - 采用与主界面一致的Nav
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Grid.Row="0" Text="检测间隙(米)" Style="{StaticResource StatisticLabelStyle}"/>
|
||||
<TextBlock Grid.Row="1" Text="{Binding DetectionGap}" Style="{StaticResource StatisticValueStyle}"/>
|
||||
<TextBlock Grid.Row="1" Text="{Binding DetectionGap, StringFormat={}{0:F2}}" Style="{StaticResource StatisticValueStyle}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</UniformGrid>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user